#!/usr/bin/perl
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
# $Header: //gpsql/feature/hd2.0/private-lili/src/include/catalog/tidycat.pl#2 $
#
# SLZY_HDR_END

use POSIX;
use Pod::Usage;
use Getopt::Long;
use Data::Dumper;
use strict;
use warnings;

# SLZY_POD_HDR_BEGIN
# WARNING: DO NOT MODIFY THE FOLLOWING POD DOCUMENT:
# Generated by sleazy.pl version 6 (release Mon Aug 20 12:30:03 2012)
# Make any changes under SLZY_TOP_BEGIN/SLZY_LONG_BEGIN

=head1 NAME

B<tidycat.pl> - generate catalog entries

=head1 VERSION

 This document describes version 34 of tidycat.pl, released
 Thu Oct 18 15:24:46 2012.

=head1 SYNOPSIS

B<tidycat.pl> 

Options:

    -help       brief help message
    -man        full documentation
    -dumpdef    output file for dump of serialized catalog data structures
    -dumpformat format options for dump file [perl, jason]
    -sqldef     output file for dump of catalog DDL statements
    -syscache   build syscache entries

=head1 OPTIONS

=over 8

=item B<-help>

    Print a brief help message and exits.

=item B<-man>

    Prints the manual page and exits.

=item B<-dumpdef> <filename>

        
Specify an optional filename to hold a dump of the serialized catalog
data structures.  The format of the dump file is determined by
dumpformat

=item B<-dumpformat> <filename>

    Specify a format for the dumpfile.  The only valid options are jason or perl.

=item B<-sqldef> <filename>

        
Specify an optional filename to hold the CREATE TABLE statements from
the tidycat definitions.  Note that these statements will contain the
tidycat WITH clause, which is not valid SQL.

=item B<-syscache>

    
If specified, rebuild syscache.h and syscache.c.  Note that this
option, like dumpdef, must read all catalog headers, ie in
src/include/catalog, the command:

   perl tidycat.pl -syscache *.h

constructs new versions of syscache.c and syscache.h.  

NOTE: Modification and extension of syscache entries is extremely rare.  
Usage of this option is discouraged.




=back

=head1 DESCRIPTION

tidycat.pl handles all of your stinky catalog problems, leaving a
fresh, clean scent.  Catalog tables require several sets of
co-ordinated modifications to multiple source files to define the
table and indexes, and (under some circumstances) the toast tables and
indexes (in toasting.h and toasting.c), as well as some special code
in catalog.c and bootparse.y to aid in bootstrap and upgrade.  tidycat
also updates a generated list of headers in pg_tidycat.h and the
catalog Makefile.  The original files are copied to a special
tidycat_backup directory in /tmp, and all generated files are written
to /tmp.  tidycat.pl uses a single definition statement to generate
the code associated with the table in multiple source files.  A sample
definition for the fictional pg_foobar.h follows:

  /* TIDYCAT_BEGINDEF

   CREATE TABLE pg_foobar
   with (camelcase=FooBar, shared=true, oid=true, relid=9991, reltype_oid=9992)
   (
   fooname name, -- name of foo bar
   foolimit real, -- max active count limit
   fooignore boolean, -- ignore foo in baz context
   );

   create unique index on pg_foobar(oid) with (indexid=9993);
   create index on pg_foobar(fooname) with (indexid=9994);

   TIDYCAT_ENDDEF
  */

The definition must begin and end with the
TIDYCAT_BEGINDEF/TIDYCAT_ENDDEF exactly as shown.  The CREATE TABLE
statement is almost identical to standard SQL, with the addition of a
special WITH clause for implementation-specific features of the
catalog entry.  Currently, the relid and reltype_oid must be specified
using unassigned oids from the unused_oids script.  The options are:

=over 8

=item CamelCase: (optional)

If your tablename is a compound name, the index definitions look a
little nicer if you define an appropriate camelcase name.  Otherwise,
the default version of the name is the tablename, minus the "pg_" prefix, 
initial letter capitalized.

=item shared: (false by default)

Whether the table is local to each database or shared by all.

=item oid: (true by default)

Whether the table has an auto-generated oid column.

=item relid: (required)

The relid of the table in pg_class.  Use unused_oids to find one.

=item reltype_oid: (required for all post-3.3 tables)

The static reltype in pg_type (necessary for upgrade).  Use
unused_oids to find one.

=item toast_oid: (required for all tables with text or array columns)

The oid of the toast table (see toasting.h).  Use unused_oids to find
one.  tidycat will automatically detect if the table definition
requires a toast table and return an error if it is not specified.

=item toast_index: (required for all tables with toast_oid)

The oid of the index of the toast table (see toasting.h).  Use
unused_oids to find one.

=item toast_reltype: (required for all toast tables post-3.3)

The static reltype of the toast table in pg_type (necessary for
upgrade). Use unused_oids to find one.

=item content: (optional)

The "content" is only for catalog tables with non-standard content
management.  "Normal" catalog tables are replicated from the master to
all the segments.  Non-standard tables fall into three categories:
MASTER_ONLY, SEGMENT_LOCAL, and PERSISTENT. Don't add any new
non-standard tables.  Please.  Note that this flag controls the
generation of validation logic for checkcat; it does not control the
catalog table tuple replication mechanisms.

=back

Similarly, index definitions are unique or non-unique, and require an
indexid (and an optional indexname).

Running tidycat.pl against pg_foobar.h adds the following section
after the definition:

  /* TIDYCAT_BEGIN_CODEGEN 
  
     WARNING: DO NOT MODIFY THE FOLLOWING SECTION: 
     Generated by tidycat.pl version 3.
     on Tue Dec  8 12:50:21 2009
  */
  
  
  /*
   TidyCat Comments for pg_foobar:
    Table is shared, so catalog.c:IsSharedRelation is updated.
    Table has an Oid column.
    Table has static type (see pg_types.h).
  
  */
  
  /* ----------------
   *		pg_foobar definition.  cpp turns this into
   *		typedef struct FormData_pg_foobar
   * ----------------
   */
  #define FooBarRelationId	9991
  
  CATALOG(pg_foobar,9991) BKI_SHARED_RELATION
  {
  	NameData	fooname;	/* name of foo bar */
  	float4		foolimit;	/* max active count limit */
  	bool		fooignore;	/* ignore foo in baz context */
  } FormData_pg_foobar;
  
  
  /* ----------------
   *		Form_pg_foobar corresponds to a pointer to a tuple with
   *		the format of pg_foobar relation.
   * ----------------
   */
  typedef FormData_pg_foobar *Form_pg_foobar;
  
  
  /* ----------------
   *		compiler constants for pg_foobar
   * ----------------
   */
  #define Natts_pg_foobar				3
  #define Anum_pg_foobar_fooname		1
  #define Anum_pg_foobar_foolimit		2
  #define Anum_pg_foobar_fooignore	3
  
  
  /* TIDYCAT_END_CODEGEN */
  

The generated code contains a CATALOG macro/struct definition for the
table, where the SQL datatypes are converted to C types.  The naming
and comments follow established conventions.

Additional modifications are made to indexing.h:

  /* relation id: 9991 - pg_foobar 20091208 */
  DECLARE_UNIQUE_INDEX(pg_foobar_oid_index, 9993, on pg_foobar using btree(oid oid_ops));
  #define FooBarOidIndexId 9993

  /* relation id: 9991 - pg_foobar 20091208 */
  DECLARE_INDEX(pg_foobar_fooname_index, 9994, on pg_foobar using btree(fooname name_ops));
  #define FooBarFoonameIndexId 9994


And the function IsSharedRelation() in catalog.c:
  bool 
  IsSharedRelation(Oid relationId)
  {
      /* These are the shared catalogs (look for BKI_SHARED_RELATION) */
      if (relationId == AuthIdRelationId ||
	(...much code...)
  /* relation id: 9991 - pg_foobar 20100105 */
  relationId == FooBarRelationId ||

Note that IsSharedRelation is only updated for shared tables.

And Boot_CreateStmt in bootparse.y:

  Boot_CreateStmt:
	  	  XCREATE optbootstrap optsharedrelation optwithoutoids boot_ident oidspec LPAREN
				{
	(...much code...)
  /* relation id: 9991 - pg_foobar 20100105 */
                            case FooBarRelationId:
                                typid = PG_FOOBAR_RELTYPE_OID;
                                break;

And pg_type.h:

  /* relation id: 9991 - pg_foobar 20100105 */
  DATA(insert OID = 9992 ( pg_foobar      PGNSP PGUID -1 f c t \054 9991 0 
	   record_in record_out record_recv record_send - d x f 0 -1 0 
       _null_ _null_ ));
  #define PG_FOOBAR_RELTYPE_OID 9992


=head2 JSON document

In src/include/catalog, the command:

   perl tidycat.pl -dd foo.json -df json *.h

will generate a JSON document describing all of the catalog tables.
This file is installed under tools/bin/gppylib/data, and gpcheckcat
uses this data to generate check queries for foreign key constraint.

=head1 CAVEATS

tidycat does not modify the original files -- it writes modified
versions of the files to /tmp.  You need to copy over the originals
with the generated files manually.  If you need to restore the
originals you can use the copies from the tidycat_backup directory.
Multiple cycles of tidycat with changing definitions can leave junk in
/tmp, and you might copy that junk into you source tree.  Do not copy
over any generated files that are older than your latest backup
directory.


=head1 AUTHORS

Apache HAWQ

Address bug reports and comments to: dev@hawq.apache.org

=cut
# SLZY_POD_HDR_END


my $glob_id = "";

my $glob_platform;
my $glob_tabwidth = 4;
my $glob_faketab = 0;
my $glob_tabstr = "\t";
#my $glob_tabstr = " " x $glob_tabwidth; 
my $glob_tmpdir = "/tmp";

# SLZY_GLOB_BEGIN
my $glob_glob;
# SLZY_GLOB_END

sub glob_validate
{

	if ($glob_glob->{dumpdef} && 
		defined($glob_glob->{dumpformat}) &&
		length($glob_glob->{dumpformat}))
	{
		die ("bad dump format: $glob_glob->{dumpformat}")
			unless ($glob_glob->{dumpformat} =~ m/jason|json|perl/i);
	}
	else
	{
		$glob_glob->{dumpformat} = "perl";
	}


#    print "loading...\n" ;
}

# SLZY_CMDLINE_BEGIN
# WARNING: DO NOT MODIFY THE FOLLOWING SECTION:
# Generated by sleazy.pl version 6 (release Mon Aug 20 12:30:03 2012)
# Make any changes under SLZY_TOP_BEGIN/SLZY_LONG_BEGIN
# Any additional validation logic belongs in glob_validate()

BEGIN {
	    my $s_help          = 0;     # brief help message
	    my $s_man           = 0;     # full documentation
	    my $s_dumpdef;               # output file for dump of serialized catalog data structures
	    my $s_dumpformat;            # format options for dump file [perl, jason]
	    my $s_sqldef;                # output file for dump of catalog DDL statements
	    my $s_syscache      = 0;     # build syscache entries

	my $slzy_argv_str;
	$slzy_argv_str = quotemeta(join(" ", @ARGV))
		if (scalar(@ARGV));

    GetOptions(
		'help|?'                     =>     \$s_help,
		'man'                        =>     \$s_man,
		'dumpdef|dd:s'               =>     \$s_dumpdef,
		'dumpformat|df|dumpfmt:s'    =>     \$s_dumpformat,
		'sqldef:s'                   =>     \$s_sqldef,
		'syscache'                   =>     \$s_syscache,
               )
        or pod2usage(2);

	pod2usage(-msg => $glob_id, -exitstatus => 1) if $s_help;
	pod2usage(-msg => $glob_id, -exitstatus => 0, -verbose => 2) if $s_man;
	
	
	$glob_glob = {};
	
	
	# version and properties from json definition
	$glob_glob->{_sleazy_properties} = {};
	$glob_glob->{_sleazy_properties}->{version} = '34';
	$glob_glob->{_sleazy_properties}->{COPYDATES} = '2009-2012';
	$glob_glob->{_sleazy_properties}->{slzy_date} = '1350599086';
	$glob_glob->{_sleazy_properties}->{slzy_argv_str} = $slzy_argv_str;
	
	$glob_glob->{dumpdef}        =  $s_dumpdef      if (defined($s_dumpdef));
	$glob_glob->{dumpformat}     =  $s_dumpformat   if (defined($s_dumpformat));
	$glob_glob->{sqldef}         =  $s_sqldef       if (defined($s_sqldef));
	$glob_glob->{syscache}       =  $s_syscache     if (defined($s_syscache));
	
	glob_validate();


}
# SLZY_CMDLINE_END



# DO NOT extend this list!  To ensure smooth upgrade, all new tables
# must have a static reltype_oid
my %dynamic_reltype_h = 
	(
 5000 => "gp_configuration",
 5002 => "gp_distribution_policy",
 5008 => "gp_master_mirroring",
 5003 => "gp_version_at_initdb",
 2600 => "pg_aggregate",
 2601 => "pg_am",
 2602 => "pg_amop",
 2603 => "pg_amproc",
 6105 => "pg_appendonly",
 2604 => "pg_attrdef",
 1249 => "pg_attribute", # special bootstrap case
 1261 => "pg_auth_members",
 1260 => "pg_authid",
 1248 => "pg_autovacuum",
 2605 => "pg_cast",
 1259 => "pg_class", # special bootstrap case
 2606 => "pg_constraint",
 2607 => "pg_conversion",
 1262 => "pg_database",
 2608 => "pg_depend",
 2609 => "pg_description",
 6040 => "pg_exttable",
 2610 => "pg_index",
 2611 => "pg_inherits",
 2612 => "pg_language",
 2613 => "pg_largeobject",
 2614 => "pg_listener",
 2615 => "pg_namespace",
 2616 => "pg_opclass",
 2617 => "pg_operator",
 5010 => "pg_partition",
 5011 => "pg_partition_rule",
 1136 => "pg_pltemplate",
 1255 => "pg_proc", # special bootstrap case
 2618 => "pg_rewrite",
 1214 => "pg_shdepend",
 2396 => "pg_shdescription",
 2619 => "pg_statistic",
 1213 => "pg_tablespace",
 2836 => "pg_toast_1255",
 2842 => "pg_toast_1260",
 2844 => "pg_toast_1262",
 2846 => "pg_toast_2396",
 2830 => "pg_toast_2604",
 2832 => "pg_toast_2606",
 2834 => "pg_toast_2609",
 2838 => "pg_toast_2618",
 2840 => "pg_toast_2619",
 2620 => "pg_trigger",
 1247 => "pg_type", # special bootstrap case
 5004 => "pg_window"
	);


# DO NOT extend this list!  To ensure smooth upgrade, all new tables
# with text or array columns must have toast tables and indexes
my %toast_tab_exception_h = 
	(
	 "gp_configuration_history" => 1,
	 "gp_configuration" => 1,
	 "gp_distribution_policy" => 1,
	 "gp_master_mirroring" => 1,
	 "gp_persistent_filespace_node" => 1,
	 "gp_san_configuration" => 1,
	 "gp_version_at_initdb" => 1,
	 "pg_aggregate" => 1,
	 "pg_appendonly" => 1,
	 "pg_appendonly_alter_column" => 1,
	 "pg_class" => 1,
	 "pg_exttable" => 1,
	 "pg_foreign_data_wrapper" => 1,
	 "pg_foreign_server" => 1,
	 "pg_foreign_table" => 1,
	 "pg_index" => 1,
	 "pg_partition_rule" => 1,
	 "pg_pltemplate" => 1,
	 "pg_stat_last_operation" => 1,
	 "pg_stat_last_shoperation" => 1,
	 "pg_tablespace" => 1,
	 "pg_type" => 1,
	 "pg_user_mapping" => 1
	);

# DO NOT extend this list!  To ensure smooth upgrade, all new toast
# tables must have a fixed reltype in pg_type.h
my %toast_reltype_exception_h = 
	(
	 "pg_attrdef" => 1,
	 "pg_constraint" => 1,
	 "pg_description" => 1,
	 "pg_proc" => 1,
	 "pg_rewrite" => 1,
	 "pg_statistic"=> 1,
	 "pg_authid" => 1,
	 "pg_database" => 1,
	 "pg_shdescription" => 1
	);

# DO NOT extend this list!  All new tidycat files should get
# registered in pg_tidycat.h
my %allfiles_exception_h = 
	(
	 "pg_aggregate.h" => 1,
	 "pg_am.h" => 1,
	 "pg_amop.h" => 1,
	 "pg_amproc.h" => 1,
	 "pg_aoseg.h" => 1,
	 "pg_appendonly.h" => 1,
	 "pg_appendonly_alter_column.h" => 1,
	 "pg_attrdef.h" => 1,
	 "pg_attribute.h" => 1,
	 "pg_auth_members.h" => 1,
	 "pg_authid.h" => 1,
	 "pg_autovacuum.h" => 1,
	 "pg_cast.h" => 1,
	 "pg_class.h" => 1,
	 "pg_constraint.h" => 1,
	 "pg_conversion.h" => 1,
	 "pg_database.h" => 1,
	 "pg_depend.h" => 1,
	 "pg_description.h" => 1,
	 "pg_extprotocol.h" => 1,
	 "pg_exttable.h" => 1,
	 "pg_filespace.h" => 1,
	 "pg_filespace_entry.h" => 1,
	 "pg_index.h" => 1,
	 "pg_inherits.h" => 1,
	 "pg_language.h" => 1,
	 "pg_largeobject.h" => 1,
	 "pg_listener.h" => 1,
	 "pg_namespace.h" => 1,
	 "pg_opclass.h" => 1,
	 "pg_operator.h" => 1,
	 "pg_partition.h" => 1,
	 "pg_partition_rule.h" => 1,
	 "pg_pltemplate.h" => 1,
	 "pg_proc.h" => 1,
	 "pg_resqueue.h" => 1,
	 "pg_rewrite.h" => 1,
	 "pg_shdepend.h" => 1,
	 "pg_shdescription.h" => 1,
	 "pg_statistic.h" => 1,
	 "pg_tablespace.h" => 1,
	 "pg_trigger.h" => 1,
	 "pg_type.h" => 1,
	 "pg_user_mapping.h" => 1,
	 "pg_window.h" => 1
 	);


sub getcomment1
{
	my $tname = shift;

	my $bigstr = <<"EOF_bigstr";
/* ----------------
 *TWOTABSTABLENAME definition.  cpp turns this into
 *TWOTABStypedef struct FormData_TABLENAME
 * ----------------
 */
EOF_bigstr

my $twotabs = $glob_tabstr x 2;

$bigstr =~ s/TWOTABS/$twotabs/gm;
$bigstr =~ s/TABLENAME/$tname/gm;

return $bigstr;

}

sub getcomment2
{
	my ($tname, $tform) = @_;

	my $bigstr = <<"EOF_bigstr";
/* ----------------
 *TWOTABSTFORM corresponds to a pointer to a tuple with
 *TWOTABSthe format of TABLENAME relation.
 * ----------------
 */
EOF_bigstr

my $twotabs = $glob_tabstr x 2;

$bigstr =~ s/TWOTABS/$twotabs/gm;
$bigstr =~ s/TABLENAME/$tname/gm;
$bigstr =~ s/TFORM/$tform/gm;

return $bigstr;

}
sub getcomment3
{
	my $tname = shift;

	my $bigstr = <<"EOF_bigstr";
/* ----------------
 *TWOTABScompiler constants for TABLENAME
 * ----------------
 */
EOF_bigstr

my $twotabs = $glob_tabstr x 2;

$bigstr =~ s/TWOTABS/$twotabs/gm;
$bigstr =~ s/TABLENAME/$tname/gm;

return $bigstr;

}

sub sqltype_to_ctype
{
	my $coltype = shift;
	my $ctype;

#	print $coltype, "\n";

	# list of valid C types for SQL types.  
	#
	# NOTE: not all sql types are valid for catalog tables, 
	# so do *NOT* extend this list unless you know what you are doing!!
	my %sql2ch =
		(
		 aclitem				  => "aclitem",
		 bigint					  => "bigint",
		 bool					  => "bool", # boolean is the real sqltype
		 boolean				  => "bool", 
		 bytea					  => "bytea",

		 # Note: a quoted_char (or "char") is a single C char
		 quoted_char			  => "char",

		 gpxlogloc				  => "gpxlogloc",
		 int2vector				  => "int2vector",
		 integer				  => "int4",
		 name					  => "NameData",
		 oid					  => "Oid",
		 oidvector				  => "oidvector",
		 real					  => "float4",
		 regproc				  => "regproc",
		 smallint				  => "int2",
		 text					  => "text",
		 tid					  => "tid",

		 # NOTE: both time and timestamp use a sleazy hack due to
		 # bootstrap nonsense
		 time					  => "time",
		 timestamp_with_time_zone => "timestamptz",

		 xid					  => "xid",
		);

	# "[]" for optional "array of" suffix

	my $isarray = ($coltype =~ m/\[\]$/);

	$coltype =~ s/\[\]$//;

	die "no C conversion for type: $coltype"
		unless (exists($sql2ch{$coltype}));

	$ctype = $sql2ch{$coltype};

	# make an array of 1
	$ctype .= "[1]" if ($isarray);

	return $ctype;
}

sub ctype_to_btree_op_prefix
{
	my $ctype = shift;

	return $ctype
		unless ($ctype =~ m/Oid|NameData|regproc/);

	$ctype = "name" if ($ctype =~ m/NameData/);
	$ctype = "oid" if ($ctype =~ m/Oid/);

	# NOTE: regproc is an oid typedef
	$ctype = "oid" if ($ctype =~ m/regproc/);

	return $ctype;
}

sub parseindex
{
	my ($alltabs, $bigstr) = @_;
	my $bUnique = 0;

	$bUnique = ($bigstr =~ m/create\s+unique\s+index/i);

	my $idef = {unique => $bUnique, with => {} };

	$bigstr =~ s/^\s*create\s+(unique\s+)?index\s+on//i;

#	print "f:",$bigstr,":f\n";

#	my @foo = ($bigstr =~ m/^s*(\w+)\s*\((.*)\)\s*(with\s*\(.*\))?/im);
#	my @foo = ($bigstr =~ m/\s*(\w+)\s*\(((?<!\)).*\))/i);
#   tablename (col1 (, col2)) 
	my @foo = ($bigstr =~ m/\s*(\w+)\s*\((\w+(\s*\,\s*\w+)*)\)/i);

#	print Data::Dumper->Dump(\@foo), "\n";

	die "bad index def: $bigstr" unless (2 < scalar(@foo));

	my $tname = shift @foo;
	my $colnamlist = shift @foo;
	my @cols = split(",", $colnamlist);

	my $icols = [];

	$tname = lc($tname);
	$tname =~ s/^\s+//;
	$tname =~ s/\s+$//;

	die "bad index def - no such table: $tname" unless (exists($alltabs->{$tname}));

	for my $c1 (@cols)
	{
		$c1 = lc($c1);
		$c1 =~ s/^\s+//;
		$c1 =~ s/\s+$//;

		die "bad index for $tname - no such col: $c1" 
			unless (exists($alltabs->{$tname}->{colh}->{$c1}));

		my $colop = $alltabs->{$tname}->{colh}->{$c1};

		$colop = ctype_to_btree_op_prefix($colop);
		$colop = $colop . "_ops";

		push @{$icols}, [$c1, $colop];
	}

	@foo = split(/\)/, $bigstr, 2);

	my $with = pop @foo;

#	print "WITH: $with \n";

	my @baz = ($with =~ m/^\s*(with\s*\(.*\))/is);

	die "bad index for $tname - no index oid: $bigstr" unless (scalar(@baz));

	my $indwithclause = shift @baz;

	$indwithclause =~ s/^\s*with\s*\(//is;
	$indwithclause =~ s/\)\s*$//s;

	@baz = split(",", $indwithclause);

	for my $withdef (@baz)
	{
		my @bzz = split("=", $withdef, 2);

		die "bad index with def for $tname: $withdef" unless (2 == scalar(@bzz));

		my $kk = shift @bzz;
		my $vv = shift @bzz;

		$kk =~ s/^\s+//;
		$kk =~ s/\s+$//;
		$kk = lc($kk);

		$vv =~ s/^\s+//;
		$vv =~ s/\s+$//;

		$idef->{with}->{$kk} = $vv;
	}

	$alltabs->{$tname}->{indexes} = []
		unless (exists(	$alltabs->{$tname}->{indexes}));
	
	$idef->{cols} = $icols;

	die "bad index def for $tname - no index oid: $bigstr" unless (exists($idef->{with}->{indexid}));

	$idef->{indexid} = $idef->{with}->{indexid};
	if (exists($idef->{with}->{indexname}))
	{
		$idef->{indexname} = $idef->{with}->{indexname};
	}

	push @{$alltabs->{$tname}->{indexes}}, $idef;
} # end parseindex

# parse foreign key definitions
sub parsefk
{
	my ($alltabs, $bigstr, $filnam) = @_;

	my @baz = ($bigstr =~ m/^\s*alter\s+table\s+(\w+)\s+add\s+(vector\_)?fk/i);

	my $tname = shift @baz;

	die "$filnam: bad foreign key - no such table: $tname" unless (exists($alltabs->{$tname}));

	# special case of vector fk for oidvector, oid array
	my $isvector = ($bigstr =~ m/add\s+vector\_fk/);

	$bigstr =~ s/^\s*alter\s+table\s+\w+\s+add\s+(vector\_)?fk//i;

	my @foo;

	# allow composite key, eg "add fk (k1[,k2...]) on ..."
	if ($bigstr =~ 
		m/\s*\(\s*(\w+(?:\s*,\s*\w+)*)\s*\)\s*on\s+(\w+)\s*\(\s*(\w+(?:\s*\,\s*\w+)*\s*)\)/i)
	{
		@foo = 
			($bigstr =~ 
			 m/\s*\(\s*(\w+(?:\s*,\s*\w+)*)\s*\)\s*on\s+(\w+)\s*\(\s*(\w+(?:\s*\,\s*\w+)*)\s*\)/i);
	}
	else # single key, no parents, ie "add fk k1 on ..."
	{
		@foo = 
			($bigstr =~ m/\s*(\w+)\s+on\s+(\w+)\s*\(\s*(\w+(?:\s*\,\s*\w+)*)\s*\)/i);
	}
#	print Data::Dumper->Dump(\@foo), "\n";

	die "$filnam: bad foreign key for table $tname: $bigstr" unless (2 < scalar(@foo));

	$alltabs->{$tname}->{foreign_keys} = []
		unless (exists($alltabs->{$tname}->{foreign_keys}));

	# NOTE: fk_list format supercededs original "foreign_keys" array...
	$alltabs->{$tname}->{fk_list} = []
		unless (exists($alltabs->{$tname}->{fk_list}));

	my $fknamlist  = shift @foo; # the foreign key column list
	my $pktname	   = shift @foo; # the primary key table name
	my $colnamlist = shift @foo; # the primary key cols

	# remove leading/trailing spaces around comma as well
	my @cols   = split(/\s*,\s*/, $colnamlist);
	my @fkcols = split(/\s*,\s*/, $fknamlist);
	
	for my $fkname (@fkcols)
	{
		die "$filnam: bad foreign key for table $tname - no such column: $fkname" 
			unless (exists($alltabs->{$tname}->{colh}->{$fkname}));
	}

	# XXX XXX: allow a "vector" fk for oidvector or oid array
	if ($isvector)
	{
		die "$filnam: bad vector foreign key for table $tname - too many columns"
			unless (1 == scalar(@fkcols));

		my $fkname = $fkcols[0];

		die "$filnam: bad vector foreign key $fkname for table $tname - must be an Oid vector or array: " 
			unless ($alltabs->{$tname}->{colh}->{$fkname} =~ 
					m/^(Oid\[1\]|oidvector)$/);

	}

	# old-style foreign key def only for regular, "scalar" keys
	push @{$alltabs->{$tname}->{foreign_keys}}, [\@fkcols, $pktname, \@cols]
		unless ($isvector);

	my $fkh = {type => "scalar", 
			   fkcols => \@fkcols, pktable => $pktname, pkcols => \@cols
	};

	$fkh->{type} = "vector" if ($isvector);

	push @{$alltabs->{$tname}->{fk_list}}, $fkh;
} # end parsefk

sub parsecols
{
	my ($alltabs, $tname, $bigstr) = @_;

#	print $bigstr;

	$bigstr =~ s/^\s*\(//s;
	$bigstr =~ s/\)\s*$//s;

#	print $bigstr;

	my @foo = split(/\n/, $bigstr);

	my $collist = [];
	my $ii = 0;

	my $tzcolname;
	my $tmcolname;

	my $precomment;

	my $colh = {}; # column data hash

	for my $lin (@foo)
	{
#		print $lin,"\n";

		unless (length($lin))
		{
			$precomment = ""
				unless (defined($precomment));

			# mark the blank lines (remove this later)
			$precomment .= "\n**TK_BLANK_LINE**";
			next;
		}

		if (($lin =~ m/^\s*\-\-/) || ($lin =~ m/^\s+$/))
		{
			$precomment = ""
				unless (defined($precomment));

			chomp($lin);
			$precomment .= "\n" . $lin;
		}
		else
		{
			# (optional quoted) word space word (or word space "char")
			die "bad col: $lin"
				unless ($lin =~ m/(\s*(\")*\w+(\")*\s+\w+)|\s*\w+\s+\"char\"/);

			# Note: timestamp fix - make into a single token
			$lin =~ 
				s/timestamp\s+with\s+time\s+zone/timestamp_with_time_zone/igm;

			# Note: quoted char (ie "char") substitution --
			# distinguish unquoted sql char, which is "character(1)",
			# or sql type bpchar, from postgresql-specific 
			# quoted char ("char"), which is a single C char
			$lin =~ 
				s/\"char\"/quoted_char/igm;

			# in sql, have a 
			# colname (with optional quotes) space coltype (followed
			# by optional array brackets), optionally followed by a
			# comma and/or a sql comment
			my @baz = 
				($lin =~ m/\s*((?:\")*\w+(?:\")*)\s+(\w+(?:\[\])?)\s*(\,)?\s*(\-\-.*)?/);

#			print Data::Dumper->Dump(\@baz), "\n";

			my $colname = shift @baz ;
			my $coltype = lc (shift @baz );

			# downcase colname unless it is quoted
			if ($colname !~ m/^\".*\"$/)
			{
				$colname = lc ($colname);
			}
			else
			{
				# remove quotes
				$colname =~ s/^\"(.*)\"$/$1/;
			}

			die ("duplicate colname: $colname for $tname")
				if (exists($colh->{$colname}));

			shift @baz ; # comma or undef

			my $postcomment; # the comment trailing the definition

			$postcomment = $baz[0] if (scalar(@baz) && defined($baz[0]));
			
			my $coldef = {colname => $colname, sqltype => $coltype};

			my $ctype = sqltype_to_ctype($coltype);
			$coldef->{ctype} = $ctype;

			$colh->{$colname} = $ctype;

			# track any timestamp columns specially
			if ($ctype =~ m/timestamp/)
			{
				if (defined($tzcolname))
				{
					# print '"colname" et al' if more than one
					# timestamp col
					$tzcolname .= " et al"
						unless ($tzcolname =~ m/et al$/);
				}
				else
				{
					$tzcolname = '"' . $colname . '"';
				}
			}
			# track any TimeADT columns specially
			if ($ctype =~ m/time$/)
			{
				if (defined($tmcolname))
				{
					# print '"colname" et al' if more than one
					# timestamp col
					$tmcolname .= " et al"
						unless ($tmcolname =~ m/et al$/);
				}
				else
				{
					$tmcolname = '"' . $colname . '"';
				}
			}

			$coldef->{postcomment} = $postcomment if (defined($postcomment));
			$coldef->{precomment} = $precomment if (defined($precomment));

			push @{$collist}, $coldef;

			$ii++;
			undef $precomment;
		}

	} # end for my lin
	
	# add the Oid column
	if ($alltabs->{$tname}->{with}->{oid})
	{
		die "name conflict: cannot have named oid column"
			if (exists($colh->{oid}));

		$colh->{oid} = "Oid";
	}

	$alltabs->{$tname}->{cols} = $collist;
	$alltabs->{$tname}->{colh} = $colh;

	# hack to track timestamp
	$alltabs->{$tname}->{tzhack} = $tzcolname
		if (defined($tzcolname));
	$alltabs->{$tname}->{tmhack} = $tmcolname
		if (defined($tmcolname));

#	print Data::Dumper->Dump(\@foo), "\n";

} # end parsecols

sub parsetab
{
	my ($alltabs, $bigstr, $filnam) = @_;
	
	my $tabdef = $bigstr;

	$bigstr =~ s/create\s+table//is;
	$bigstr =~ s/^\s+//s;
	$bigstr =~ s/\s+$//s;

	my @foo = ($bigstr =~ m/^\s*(\w+)/s);

	die "no tablename: $tabdef" unless (scalar(@foo));

	my $tname = shift @foo;

	$bigstr =~ s/$tname//s;
	$bigstr =~ s/^\s+//s;

	$tname =~ s/^\s+//;
	$tname =~ s/\s+$//;
	$tname = lc($tname);

	$alltabs->{$tname} = {tabdef_text => $tabdef, filename => $filnam };

	if ($bigstr =~ m/\s*with\s*\(.*\)/is)
	{
		# match a "WITH (...)" followed by "(" 
		@foo = ($bigstr =~ m/^\s*(with\s*\(.*\)(?=(\s*\()))/is);

		die "bad with: $bigstr" unless (scalar(@foo));

		my $withclause  = shift @foo;

		$alltabs->{$tname}->{with} = {text => $withclause};

		$bigstr =~ s/\s*(with\s*\(.*\)(?=(\s*\()))//is;
		$bigstr =~ s/^\s+//s;

		$withclause =~ s/^\s*with\s*\(//is;
		$withclause =~ s/\)\s*$//s;

		my @foo = split(",", $withclause);

		for my $withdef (@foo)
		{
			my @baz = split("=", $withdef, 2);

			die "bad with def: $withdef" unless (2 == scalar(@baz));

			my $kk = shift @baz;
			my $vv = shift @baz;

			$kk =~ s/^\s+//;
			$kk =~ s/\s+$//;
			$kk = lc($kk);

			$vv =~ s/^\s+//;
			$vv =~ s/\s+$//;

			$alltabs->{$tname}->{with}->{$kk} = $vv;

		}
		
		die "no relid for table $tname"
			unless (exists($alltabs->{$tname}->{with}->{relid}));

		# build the common comment tag 
		# (of the form /* relation id: nnn - tablename date */ ) 
		# This comment is affixed to generated code (and is used as a
		# marker to find these entries when we need to cleanout
		# duplicates)

		$alltabs->{$tname}->{relid_comment_tag} = 
			"/* relation id: " .
			$alltabs->{$tname}->{with}->{relid} .
			" - $tname " .
			yyyy_mm_dd() . " */\n";

		if (exists($alltabs->{$tname}->{with}->{shared}))
		{
			$alltabs->{$tname}->{with}->{shared} =
				($alltabs->{$tname}->{with}->{shared} =~ m/t|true|y|yes|1/i);
		}
		else
		{
			$alltabs->{$tname}->{with}->{shared} = 0;
		}

		if (exists($alltabs->{$tname}->{with}->{bootstrap}))
		{
			$alltabs->{$tname}->{with}->{bootstrap} =
				($alltabs->{$tname}->{with}->{bootstrap} =~ m/t|true|y|yes|1/i);
		}
		else
		{
			$alltabs->{$tname}->{with}->{bootstrap} = 0;
		}

		if (exists($alltabs->{$tname}->{with}->{oid}))
		{
			$alltabs->{$tname}->{with}->{oid} =
				($alltabs->{$tname}->{with}->{oid} =~ m/t|true|y|yes|1/i);
		}
		else
		{
			$alltabs->{$tname}->{with}->{oid} = 1;
		}

		# build a camel-case string for the tablename
		unless (exists($alltabs->{$tname}->{with}->{camelcase}))
		{
			my $cc1 = $tname;
			my $isgp = 0;

			if ($cc1 =~ m/^(gp)\_/i)
			{
				# treat camel-case name special for Gp tables...
				$cc1 =~ s/^...//;
				$isgp = 1;
			}
			if ($cc1 =~ m/^(pg)\_/i)
			{
				# remove prefix
				$cc1 =~ s/^...//;
			}
			$cc1 = ucfirst($cc1);
			$cc1 = "Gp" . $cc1 if ($isgp);
			$alltabs->{$tname}->{with}->{camelcase} = $cc1;
		}

		if (exists($alltabs->{$tname}->{with}->{reltype_oid}))
		{
			my $relid = $alltabs->{$tname}->{with}->{relid};

			die "table $tname has a dynamic reltype oid -- cannot redefine to static!!"
				if (exists($dynamic_reltype_h{$relid}));
		}
		else
		{
			my $relid = $alltabs->{$tname}->{with}->{relid};

			die "table $tname must have a static reltype oid"
				unless (exists($dynamic_reltype_h{$relid}));
		}

	} # end if with


#	print "bigstr: ", $bigstr, "\n";

	parsecols($alltabs, $tname, $bigstr);

#	print "bigstr: ", $bigstr, "\n";

	# CONTENT checks: 
	# NOTE: no code generation - only for validation
	if (exists($alltabs->{$tname}->{with}->{content}))
	{
		die "unknown content type for $tname: $alltabs->{$tname}->{with}->{content}"
			unless ($alltabs->{$tname}->{with}->{content} =~
					m/MASTER\_ONLY|PERSISTENT|SEGMENT\_LOCAL/);
	}

	# TOAST table checks

	if (exists($alltabs->{$tname}->{with}->{toast_oid}))
	{
		die "toast table for $tname must have index (toast_index)"
			unless (exists($alltabs->{$tname}->{with}->{toast_index}));
	}
	if (exists($alltabs->{$tname}->{with}->{toast_index}))
	{
		die "toast index for $tname must have table (toast_oid)"
			unless (exists($alltabs->{$tname}->{with}->{toast_oid}));
	}
	if (exists($alltabs->{$tname}->{with}->{toast_oid}))
	{
		die "toast table for $tname must have static reltype (toast_reltype)"
			unless (exists($alltabs->{$tname}->{with}->{toast_reltype}) ||
					exists($toast_reltype_exception_h{$tname}));
	}
	if (exists($alltabs->{$tname}->{with}->{toast_reltype}))
	{
		die "toast table for $tname has dynamic reltype -- cannot redefine to static!!"
			if (exists($toast_reltype_exception_h{$tname}));

		die "toast table static reltype for $tname must have table (toast_oid)"
			unless (exists($alltabs->{$tname}->{with}->{toast_oid}));
	}

	if (exists($alltabs->{$tname}->{cols}) &&
		defined($alltabs->{$tname}->{cols}) &&
		# ignore pre-existing exception tables
		(!exists($toast_tab_exception_h{$tname})))
	{
		my $badmsg = "";

		for my $col1 (@{$alltabs->{$tname}->{cols}})
		{
			my $cname = $col1->{colname};
			my $ctype = $col1->{sqltype};

			# aclitem arrays are ok
			next if ($ctype =~ m/aclitem/);

			if (($ctype =~ m/\[.*\]/) ||
				($ctype =~ /text/))
			{
				last # table has a toast table - ok!
					if (exists($alltabs->{$tname}->{with}->{toast_oid}));

				$badmsg .= "table $tname needs toast table (toast_oid) for column $cname of type $ctype\n";
			}
		} # end for
		die $badmsg if (length($badmsg));

	}

	return $tname;

} # end parsetab


sub parsetabdef
{
	my ($alltabs, $bigstr, $filnam) = @_;
	my @keys;

	# fix for case of semicolons in comments
	{
		my @ll;
		my @lins = split(/\n/, $bigstr);

		for my $lin (@lins)
		{
			# replace semicolon in comment with "dummy semi"
			if ($lin =~ m/\-\-.*\;/)
			{
				$lin =~ s/\;/TIDY_DUMMY_SEMI/g;
			}
			push @ll, $lin;
		}
		$bigstr = join("\n", @ll);
	}

	my @statements = split(';', $bigstr);

#	print Data::Dumper->Dump(\@statements), "\n";

	for my $stat (@statements)
	{
		# replace dummy semicolons with the real thing
		$stat =~ s/TIDY\_DUMMY\_SEMI/;/gm;

		if ($stat =~ m/^\s*create\s+table/is)
		{
			push @keys, parsetab($alltabs, $stat, $filnam);
		}
		elsif ($stat =~ m/^\s*create\s+(unique\s+)?index/is)
		{
			parseindex($alltabs, $stat);
		}
#		elsif ($stat =~ m/^\s*alter\s+table\s+\w+add\s+fk/is)
		elsif ($stat =~ m/^\s*alter\s+table/is)
		{
			parsefk($alltabs, $stat, $filnam);
		}
	}

	return @keys;

} # end parsetabdef

# expects val1, len1, val2, len2
# where length values are based on printed offset, not length(val), ie
# embedded tabs are counted.
sub tabalign
{
	my ($tabwidth, $collist) = @_;
	my $tabstr = $glob_tabstr;

	my $maxlen = 0;
	for my $coldef (@{$collist})	
	{
		die "bad coldef: " . Data::Dumper->Dump([$coldef])
			unless (scalar(@{$coldef}) > 3);

		$maxlen = $coldef->[1] if ($coldef->[1] > $maxlen);
	}

	# find the tab position for the second column
	my $col2tab = (POSIX::ceil($maxlen / $tabwidth)) * $tabwidth;

	$col2tab++ if ($col2tab == $maxlen);

#	print $maxlen, " " , $col2tab, "\n";

#	print Data::Dumper->Dump($collist), "\n";

	for my $ii (0..(scalar(@{$collist})-1))
	{
#		print Data::Dumper->Dump($collist->[$ii]), "\n";

		my $val1 = shift @{$collist->[$ii]};
		my $len1 = shift @{$collist->[$ii]};
		my $val2 = shift @{$collist->[$ii]};
		my $len2 = shift @{$collist->[$ii]};

		my $newval = $val1;

		if ($len1 < $col2tab)
		{
			my $mod1 = $len1 % $tabwidth;

#			print "mod: $mod1\n";

			if ($mod1)
			{
				$len1 += ($tabwidth - $mod1);
				if ($glob_faketab)
				{
					$val1 .= " " x ($tabwidth - $mod1);
				}
				else
				{
					$val1 .= $tabstr;
				}
			}
		}

		while ($len1 < $col2tab)
		{
			$len1 += $tabwidth;
			$val1 .= $tabstr;
		}

		unshift @{$collist->[$ii]}, $len1 + $len2 ;
		unshift @{$collist->[$ii]}, $val1 . $val2;		
	} # end for ii

#	print Data::Dumper->Dump($collist), "\n";

	return $collist;
}

sub simpletabalign
{
	my ($col1, $col2) = @_;

	my $colitem = [];

	push @{$colitem}, $col1;
	push @{$colitem}, length($col1);

	push @{$colitem}, $col2;
	push @{$colitem}, length($col2);

	my $collist = tabalign($glob_tabwidth, [$colitem]);

	return $collist->[0]->[0];
}

# take a table-format string (columns separated by "|", rows separated
# by newline) and return an array with a single, formatted string row
sub tabalignstr
{
	my $str = shift;

	my $flist = [];

	my @lines = split(/\n/, $str);

	return $flist
		unless scalar(@lines);

	for my $lin (@lines)
	{
		my @foo = split(/\|/, $lin);

		last
			unless (scalar(@foo));

		my $flitem = [];
		
		for my $itm (@foo)
		{
			$itm = ""
				unless (defined($itm));
			push @{$flitem}, $itm, length($itm);
		}
		push @{$flist}, $flitem;
	}

  L_bigloop:
	while (1)
	{
		for my $coldef (@{$flist})
		{
			last L_bigloop
				unless (scalar(@{$coldef}) > 3);
		}

		$flist = tabalign($glob_tabwidth, $flist);
	}

	my @itmlst;
	for my $itm (@{$flist})
	{
		push @itmlst, shift(@{$itm});
	}

	return \@itmlst;

} # end tabalignstr

# convert a column name to an "Anum_" column id
sub anum_key
{
	my ($tname, $wkey) = @_;

	my $atname = $tname;
	
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# XXX XXX: stupid last_operation/shoperation fixup
	if ($tname =~ m/pg\_stat\_last_(sh)?operation/)
	{
		$atname =~ s/eration$//;
		$atname =~ s/stat\_last\_/statlast/;
	}

	if ($tname =~ m/gp_distribution_policy/)
	{
		$atname =~ s/distribution\_//;
	}
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 

	return "Anum_" . $atname . "_" . $wkey;
}

sub struct_form_tname
{
	my $tname = shift;

	my $atname = $tname;
	
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# XXX XXX: stupid last_operation/shoperation fixup
	if ($tname =~ m/pg\_stat\_last_(sh)?operation/)
	{
		$atname =~ s/eration$//;
		$atname =~ s/stat\_last\_/statlast/;
			}
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 

	return "Form_" . $atname;
}

sub formatcolconst
{
	my ($tnamepref, $allcol) = @_;

	my $collist = [];

	{
		my $colitem = [];
		my $cdef;

		$cdef = "#define Natts_" . $tnamepref;

		push @{$colitem}, $cdef;
		push @{$colitem}, length($cdef) ;
		push @{$colitem}, scalar(@{$allcol});
		push @{$colitem}, length(scalar(@{$allcol}));
			
		push @{$collist}, $colitem;
	}

	my $ii = 1;

	for my $coldef (@{$allcol})
	{
		my $colitem = [];
		my $cdef;

		$cdef = "#define " . anum_key($tnamepref, $coldef->{colname});

		push @{$colitem}, $cdef;
		push @{$colitem}, length($cdef) ;

		push @{$colitem}, $ii;
		push @{$colitem}, length($ii);

		$ii++;
			
		push @{$collist}, $colitem;
	}

	$collist = tabalign($glob_tabwidth, $collist);

	my $bigstr = "";

	for my $coldef (@{$collist})
	{
		my $st1 = shift @{$coldef};

		$bigstr .= $st1 . "\n";
	}

	return $bigstr;

} # end formatcolconst

sub formatcols
{
	my $allcol = shift;

	my $collist = [];

	my $bigstr = "";

	# formatting trick: col1 is prefixed by a tab, so increase its
	# length by tabwidth.  col2 is the column name followed by a
	# semicolon.  And col3 is the optional comment.  Build an array of
	# items of [val1, len1, val2, len2, val3, len3]
	#
	# tabalign will shift off the first 4 entries and append the values
	# with embedded tabs to align them, nicely, then unshift the
	# append values and combined length back into original array,
	# resulting in:
	#  ["val1<tabs>val2", len1+<tab widths>+len2, val3, len3]
	#
	# So now we just feed this array back into tabalign to get:
	#  ["val1<tabs>val2<tabs>val3", len1+<tab widths>+len2+<tab widths>+len3]

	for my $coldef (@{$allcol})
	{
		my $colitem = [];
		my $coltype = $coldef->{ctype};
		my $colname = $coldef->{colname};

		# fix arrays of types -- the array suffix moves from the
		# typename to the column name
		if ($coltype =~ m/\[1\]/)
		{
			$coltype =~ s/\[1\]//;
			$colname .= "[1]";
		}

		push @{$colitem}, $glob_tabstr . $coltype;
		push @{$colitem}, length($coltype) + $glob_tabwidth;

		push @{$colitem}, $colname . ";";
		push @{$colitem}, length($colname) + 1;

		if (exists($coldef->{postcomment}))
		{
			my $pc = $coldef->{postcomment};

			$pc =~ s/^\-\-/\/\*/;
			$pc .= " \*\/";

			push @{$colitem}, $pc;
			push @{$colitem}, length($pc);
		}
		else
		{
			push @{$colitem}, "";
			push @{$colitem}, 0;
		}

		push @{$collist}, $colitem;
	}

	$collist = tabalign($glob_tabwidth, $collist);
	$collist = tabalign($glob_tabwidth, $collist);

	for my $ii (0..(scalar(@{$collist})-1))
	{
		my $coldef1 = $allcol->[$ii];
		my $coldef2 = $collist->[$ii];
		my $st1 = shift @{$coldef2};

		if (1 && (exists($coldef1->{precomment})))
		{
			my $pc = $coldef1->{precomment};
			my $bComment = 0;

			$bComment = ($pc =~ m/^\s*\-\-/m);

			if ($bComment)
			{
				# make everything into a single-line comment
				$pc =~ s/^\s*\-\-(.*)/$glob_tabstr\/\*$1 \*\//gm;

				# find adjacent single-line comments by looking for
				# "*/\n/*" and merge them together
				$pc =~ s/\*\/\s*(\n\s*)\/\*/$1 \*/gm;

				$pc .= "\n";
			}

			# remove the blank lines
			$pc =~ s/\*\*TK\_BLANK\_LINE\*\*//gm;

			$bigstr .= $pc;
		}

		$bigstr .= $st1 . "\n";
	}

	return $bigstr;

} # end formatcols


sub formatTZcomment
{
	my $colname = shift;

	my $bigstr = <<"EOF_bigstr";
/*
 * The CATALOG definition has to refer to the type of MYTYPENAME as
 * "timestamptz" (lower case) so that bootstrap mode recognizes it.  But
 * the C header files define this type as TimestampTz.	Since the field is
 * potentially-null and therefore cannot be accessed directly from C code,
 * there is no particular need for the C struct definition to show the
 * field type as TimestampTz --- instead we just make it Datum.
 */

#define timestamptz Datum

EOF_bigstr

	$bigstr =~ s/MYTYPENAME/$colname/;
	return $bigstr;
}

sub formatTMcomment
{
	my $colname = shift;

	my $bigstr = <<"EOF_bigstr";
/*
 * The CATALOG definition has to refer to the type of MYTYPENAME as
 * "time" (lower case) so that bootstrap mode recognizes it.  But
 * the C header files define this type as TimeADT.	So we use a sleazy trick.
 *
 */

#define time TimeADT

EOF_bigstr

	$bigstr =~ s/MYTYPENAME/$colname/;
	return $bigstr;
}

# print YEARMONTHDAY as 8 digit string
sub yyyy_mm_dd
{
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
		localtime();

	# adjust year and month to normal
	$year += 1900;
	$mon++; 
	
	# format with lead zero if necessary
	$mon  = sprintf("%02d", $mon);
	$mday = sprintf("%02d", $mday);

	return $year.$mon.$mday;

}

sub fix_issharedrelation_function
{
	my $alltabs = shift;
	my $bigstr = "";
	my $toaststr = "";
	my %relidh;

	# nicer to sort by relid (vs tablename)
	while (my ($jj, $ww) = each(%{$alltabs}))
	{
		$relidh{$ww->{with}->{relid}} = $jj;
	}

#	for my $kk (sort (keys (%{$alltabs})))
	for my $jj (sort {$a <=> $b} (keys (%relidh)))
	{
		my $kk = $relidh{$jj};
		my $vv = $alltabs->{$kk};
		my $relid = $vv->{with}->{relid};
		my $rct   = $vv->{relid_comment_tag}; # standard comment

		next
			unless ($vv->{with}->{shared});

#		print "iss: ", Data::Dumper->Dump([$vv]), "\n";

		$bigstr .= "\n\n";
		$bigstr .= $rct;
		$bigstr .= "relationId == " . $vv->{CamelCaseRelationId} . " || ";
		$bigstr .= "\n";

		# build entries for Toast table and toast index
		if (exists($vv->{CamelCaseToastTab}))
		{
			$toaststr .= "\n\n";
			$toaststr .= $rct;
			$toaststr .= "relationId == " . $vv->{CamelCaseToastTab} . " || ";
			$toaststr .= "\n";
			$toaststr .= "relationId == " . $vv->{CamelCaseToastInd} . " || ";
			$toaststr .= "\n";
		}

	}

	my $indstr = "";

	for my $jj (sort {$a <=> $b} (keys (%relidh)))
	{
		my $kk = $relidh{$jj};
		my $vv = $alltabs->{$kk};
		my $relid = $vv->{with}->{relid};
		my $rct   = $vv->{relid_comment_tag}; # standard comment

		next
			unless ($vv->{with}->{shared});

		next unless (exists($vv->{indexes}));

		$indstr .= "\n\n";

		for my $ind (@{$vv->{indexes}})
		{
			$indstr .= $rct;
			$indstr .= "relationId == " . $ind->{CamelCaseIndexId} . " || " ;
			$indstr .= "\n";
		}
		$indstr .= "\n\n";
	}

	return [$bigstr, $indstr, $toaststr];
} # end fix_issharedrelation_function

sub bootparse_str
{
	my ($ccrelid, $ucreltypid) = @_;

	my $bigstr = <<"EOF_bigstr";
SEVENTABScase CAMELCASERELATIONID:
EIGHTTABStypid = UPPERCASERELTYPID;
EIGHTTABSbreak;
EOF_bigstr

	my $seventabs = $glob_tabstr x 7;
	my $eighttabs = $glob_tabstr x 8;

	$bigstr =~ s/SEVENTABS/$seventabs/gm;
	$bigstr =~ s/EIGHTTABS/$eighttabs/gm;

	$bigstr =~ s/CAMELCASERELATIONID/$ccrelid/gm;
	$bigstr =~ s/UPPERCASERELTYPID/$ucreltypid/gm;

	return $bigstr;
}

sub fix_bootparse
{
	my $alltabs = shift;
	my $bigstr = "";
	my %relidh;

	# nicer to sort by relid (vs tablename)
	while (my ($jj, $ww) = each(%{$alltabs}))
	{
		$relidh{$ww->{with}->{relid}} = $jj;
	}

#	for my $kk (sort (keys (%{$alltabs})))
	for my $jj (sort {$a <=> $b} (keys (%relidh)))
	{
		my $kk = $relidh{$jj};
		my $vv = $alltabs->{$kk};
		my $relid = $vv->{with}->{relid};
		my $rct   = $vv->{relid_comment_tag}; # standard comment

		next
			unless (exists($vv->{with}->{reltype_oid}));

		$bigstr .= "\n\n";
		$bigstr .= $rct;
		$bigstr .=  bootparse_str($vv->{CamelCaseRelationId}, 
								  $vv->{UppercaseReltypeOid});
		$bigstr .= "\n\n";
	}

	return $bigstr;

} # end fix_bootparse

sub toastbootstrap_str
{
	my ($cctoastid, $ucreltypid) = @_;

	my $bigstr = <<"EOF_bigstr";
TWOTABScase CAMELCASETOASTID:
THREETABStypid = UPPERCASERELTYPID;
THREETABSbreak;
EOF_bigstr

	my $twotabs   = $glob_tabstr x 2;
	my $threetabs = $glob_tabstr x 3;

	$bigstr =~ s/TWOTABS/$twotabs/gm;
	$bigstr =~ s/THREETABS/$threetabs/gm;

	$bigstr =~ s/CAMELCASETOASTID/$cctoastid/gm;
	$bigstr =~ s/UPPERCASERELTYPID/$ucreltypid/gm;

	return $bigstr;
}

sub fix_toastbootstrap
{
	my $alltabs = shift;
	my $bigstr = "";
	my %relidh;

	# nicer to sort by relid (vs tablename)
	while (my ($jj, $ww) = each(%{$alltabs}))
	{
		$relidh{$ww->{with}->{relid}} = $jj;
	}

#	for my $kk (sort (keys (%{$alltabs})))
	for my $jj (sort {$a <=> $b} (keys (%relidh)))
	{
		my $kk = $relidh{$jj};
		my $vv = $alltabs->{$kk};
		my $rct   = $vv->{relid_comment_tag}; # standard comment

		next
			unless (exists($vv->{with}->{toast_reltype}));

		$bigstr .= "\n\n";
		$bigstr .= $rct;
		$bigstr .=  toastbootstrap_str($vv->{CamelCaseToastTab}, 
									   $vv->{UppercaseToastReltypeOid});
		$bigstr .= "\n\n";
	}

	return $bigstr;

} # end fix_toastbootstrap

sub get_all_filenames
{
	my ($alltabs, $h_allfiles) = @_;
	
	# store the basename of the file in a hash
	for my $kk (sort (keys (%{$alltabs})))
	{
		my ($vol, $dirs, $basename) = 
			File::Spec->splitpath($alltabs->{$kk}->{filename});
		
		# skip exceptions
		next if (exists($allfiles_exception_h{$basename}));

		$h_allfiles->{$basename} = $kk;
	}

} # end get_all_filenames


sub formattypedata
{
	my ($oid, $tname, $reltype_oid) = @_;

	my $bigstr = <<'EOF_bigstr';
DATA(insert OID = RELTYPE_OID ( TABLENAME	    PGNSP PGUID -1 f c t \054 THEE_OID 0 record_in record_out record_recv record_send - d x f 0 -1 0 _null_ _null_ ));	
EOF_bigstr

$bigstr =~ s/TABLENAME/$tname/gm;
$bigstr =~ s/RELTYPE\_OID/$reltype_oid/gm;
$bigstr =~ s/THEE\_OID/$oid/gm;

return $bigstr;

}
sub formattoasttypedata
{
	my ($oid, $tname, $reltype_oid, $toast_oid, $toast_reltype) = @_;

	my $bigstr = <<'EOF_bigstr';
DATA(insert OID = TOAST_RELTYPE (pg_toast_THEE_OID TOASTNSP PGUID -1 f c t \054 TOAST_OID 0 record_in record_out record_recv record_send - d x f 0 -1 0 _null_ _null_));
EOF_bigstr

$bigstr =~ s/TABLENAME/$tname/gm;
$bigstr =~ s/RELTYPE\_OID/$reltype_oid/gm;
$bigstr =~ s/THEE\_OID/$oid/gm;
$bigstr =~ s/TOAST\_OID/$toast_oid/gm;
$bigstr =~ s/TOAST\_RELTYPE/$toast_reltype/gm;

return $bigstr;

}

sub formattypes
{
	my $alltabs = shift;
	my $bigstr = "";
	my %relidh;

	# nicer to sort by relid (vs tablename)
	while (my ($jj, $ww) = each(%{$alltabs}))
	{
		$relidh{$ww->{with}->{relid}} = $jj;
	}

#	for my $kk (sort (keys (%{$alltabs})))
	for my $jj (sort {$a <=> $b} (keys (%relidh)))
	{
		my $kk = $relidh{$jj};
		my $vv = $alltabs->{$kk};
		my $relid = $vv->{with}->{relid};
		my $rct   = $vv->{relid_comment_tag}; # standard comment

		next
			unless (exists($vv->{with}->{reltype_oid}));

		my $reltype_oid = $vv->{with}->{reltype_oid};

		my $uc_tname = uc($kk);

		$vv->{UppercaseReltypeOid} = $uc_tname . "_RELTYPE_OID";

		$bigstr .= $rct;
		$bigstr .= formattypedata($relid, $kk, $reltype_oid);
		$bigstr .= 
			"#define " . $uc_tname . "_RELTYPE_OID " . $reltype_oid . "\n\n\n";

		# do TOAST
		next
			unless (exists($vv->{with}->{toast_reltype}) &&
					exists($vv->{with}->{toast_oid}));

		$vv->{UppercaseToastReltypeOid} = $uc_tname . "_TOAST_RELTYPE_OID";
		$bigstr .= $rct;
		$bigstr .= formattoasttypedata($relid, $kk, $reltype_oid, 
									   $vv->{with}->{toast_oid},
									   $vv->{with}->{toast_reltype}
			);
		$bigstr .= 
			"#define " . $vv->{UppercaseToastReltypeOid} . " " .
			$vv->{with}->{toast_reltype} . "\n\n\n";
	}

	return $bigstr;
}

sub formatindexes
{
	my $alltabs = shift;
	my $bigstr = "";
	my %relidh;

	# nicer to sort by relid (vs tablename)
	while (my ($jj, $ww) = each(%{$alltabs}))
	{
		$relidh{$ww->{with}->{relid}} = $jj;
	}

#	for my $kk (sort (keys (%{$alltabs})))
	for my $jj (sort {$a <=> $b} (keys (%relidh)))
	{
		my $kk = $relidh{$jj};
		my $vv = $alltabs->{$kk};
		my $relid = $vv->{with}->{relid};
		my $rct   = $vv->{relid_comment_tag}; # standard comment

		next unless (exists($vv->{indexes}));

		for my $ind (@{$vv->{indexes}})
		{
			my $iname;
			my $icolist;
			my $camelname;

			$iname = $kk;
			$icolist = "";
			$camelname = $vv->{with}->{camelcase};

			for my $cols (@{$ind->{cols}})
			{
				$iname .= "_" . $cols->[0];
				$camelname .= ucfirst($cols->[0]);

				if (length($icolist))
				{
					$icolist .= ", ";
				}
				$icolist .= $cols->[0] . " " . $cols->[1];
			}
			$iname .= "_index";

			# if indexname was supplied, use it instead
			if (exists($ind->{indexname}))
			{
				$iname = $ind->{indexname};
			}

			if ($ind->{unique})
			{
				$bigstr .= $rct;
				$bigstr .= "DECLARE_UNIQUE_INDEX(";
			}
			else
			{
				$bigstr .= $rct;
				$bigstr .= "DECLARE_INDEX(";
			}
			my $indexid = $ind->{indexid};

			$bigstr .=
				$iname . ", " . "$indexid, on " . $kk . 
				" using btree(" . $icolist . "));\n";

			# use user-supplied CamelCase name if it exists
			if (exists($ind->{with}->{camelcase}))
			{
				$camelname = $ind->{with}->{camelcase};
			}

			$ind->{CamelCaseIndexId} = $camelname . "IndexId";

			my $ccdef = "#define " . $camelname . "IndexId";

			$bigstr .= simpletabalign($ccdef, $indexid) . "\n\n\n";

		}
	}

	return $bigstr;
} # end formatindexes

sub format_syscache_cacheinfo
{
	my $cinfo = shift;

	my $vv = $cinfo;

# example:
#
#	{AggregateRelationId,		/* AGGFNOID */
#		AggregateAggfnoidIndexId,
#		1,
#		{
#			Anum_pg_aggregate_aggfnoid,
#			0,
#			0,
#			0
#		},
#		32
#	},


	my $bigstr = <<"EOF_bigstr";
FIRSTLINE
TWOTABSINDID,
TWOTABSCOLCNT,
TWOTABS{
ALLCOLS
TWOTABS},
TWOTABSNBUCKETS
EOF_bigstr

	my $twotabs = $glob_tabstr x 2;

	$bigstr =~ s/TWOTABS/$twotabs/gm;

	# slight goofiness with end curly brace to fix emacs formatting...
	$bigstr .= "\t" . '}';

	$bigstr =~ s/FIRSTLINE/$vv->{firstline}/;
	$bigstr =~ s/INDID/$vv->{indid}/;
	$bigstr =~ s/NBUCKETS/$vv->{nbuckets}/;

	my $colcnt = scalar(@{$vv->{cols}});
	$bigstr =~ s/COLCNT/$colcnt/;

	# build array of length 4, with zeroes in "null" positions
	my $cols = [];
	for my $ii (0..3)
	{
		if ($ii >= $colcnt)
		{
			push @{$cols}, '0';
			next;
		}

		my $c1 = $vv->{cols}->[$ii];

		if (($c1 eq 'oid')
			&& $vv->{with_oid})
		{
			push @{$cols}, 'ObjectIdAttributeNumber';
		}
		else
		{
			push @{$cols}, anum_key($vv->{relname}, $c1);
		}
	}

	my $allcols = $glob_tabstr x 3;
	$allcols .= join(",\n\t\t\t", @{$cols});

	$bigstr =~ s/ALLCOLS/$allcols/;

	return $bigstr;

} # end format_syscache_cacheinfo

sub fixup_syscache_cfile
{
	my ($alltabs, $cacheh, $fullcname, $tmpcname, 
		$gen_hdr_str) = @_;

	my $bigstr = "";

	$bigstr = $gen_hdr_str;

	my $whole_file;
	
	{
        # $$$ $$$ undefine input record separator (\n)
        # and slurp entire file into variable
		
        local $/;
        undef $/;
		
		my $fh;

		open $fh, "< $fullcname" 
			or die "cannot open $fullcname: $!";

		$whole_file = <$fh>;
		
		close $fh;
	}

	# build array of strings of "first lines", eg
	# "{AggregateRelationId,  /* AGGFNOID */"
	# using tabalignstr
	my $tab1 = "";
	for my $kk (sort(keys(%{$cacheh})))
	{
		my $vv = $cacheh->{$kk};

		$tab1 .= "|{" . $vv->{relid} . ",|/* " . $kk . " */\n";
	}

	my $firstlin = tabalignstr($tab1);

	# format every cacheinfo entry in alphabetical order
	my @out1;
	for my $kk (sort(keys(%{$cacheh})))
	{
		my $vv = $cacheh->{$kk};

		$vv->{firstline} = shift @{$firstlin};

		push @out1, format_syscache_cacheinfo($vv);
	}

	# join the formatted entries
	$bigstr .= "\n" .
		"static const struct cachedesc cacheinfo[] = {\n" .
		join(",\n", @out1) . "\n};\n";

	my $prefx = quotemeta('TIDYCAT_BEGIN_CODEGEN');
	my $suffx = quotemeta('TIDYCAT_END_CODEGEN');

	my @zzz = ($whole_file =~ 
			   m/^\s*\/\*\s*$prefx\s*\s*$(.*)^\s*\/\*\s*$suffx\s*\*\/\s*$/ms);

	die "bad target: $whole_file"
		unless (scalar(@zzz));

	my $rex = $zzz[0];

	# replace carriage returns first, then quotemeta, then fix CR again...
	$rex =~ s/\n/SLASHNNN/gm;
	$rex = quotemeta($rex);
	$rex =~ s/SLASHNNN/\\n/gm;
	
	# substitute the new generated definitions for the prior
	# generated definitions in the target file
	$whole_file =~ s/$rex/$bigstr/ms;

	my $outi;
	
	open $outi, "> $tmpcname"
		or die "cannot open $tmpcname for write: $!";
	
	# rewrite the target file
	print $outi $whole_file;
				
	close $outi;

} # end fixup_syscache_cfile

sub fixup_syscache
{
	my ($alltabs, $fullhname, $tmphname, $fullcname, $tmpcname,
		$verzion, $nnow) = @_;

	my $bigstr = "";
	my %cacheh;

	my $gen_hdr_str = "\n * WARNING: DO NOT MODIFY THE FOLLOWING SECTION: \n" .
	" *  Generated by " . $verzion . "\n" . 
	" *  on " . $nnow . "\n*/\n";

	$bigstr = $gen_hdr_str;

	my $whole_file;
	
	{
        # $$$ $$$ undefine input record separator (\n)
        # and slurp entire file into variable
		
        local $/;
        undef $/;
		
		my $fh;

		open $fh, "< $fullhname" 
			or die "cannot open $fullhname: $!";

		$whole_file = <$fh>;
		
		close $fh;
	}

	for my $kk (keys (%{$alltabs}))
	{
		my $vv = $alltabs->{$kk};
		my $relid = $vv->{with}->{relid};
		my $rct   = $vv->{relid_comment_tag}; # standard comment

#		print $kk, "\n";

		next unless (exists($vv->{indexes}));

		for my $ind (@{$vv->{indexes}})
		{
			my $iname;
			my $icolist;
			my $camelname;

			next unless (exists($ind->{with}->{syscacheid}));

			die "$kk: invalid syscacheid for non-unique index"
				unless ($ind->{unique});

			my $cols = [];

			# get colnames
			for my $c1 (@{$ind->{cols}})
			{
				push @{$cols}, $c1->[0];
			}

			my $with_oid = (
				(exists($vv->{with}->{oid})) &&
				($vv->{with}->{oid}));

			$cacheh{$ind->{with}->{syscacheid}} = {
				relname => $kk,
				relid => $vv->{CamelCaseRelationId},
				indid => $ind->{CamelCaseIndexId},
				nbuckets => $ind->{with}->{syscache_nbuckets},
				with_oid => $with_oid,
				cols => $cols
			};
		}
	}

	# XXX XXX: not enough syscacheid ?
	return
		if (scalar(keys(%cacheh)) <= 2);

	{
		my @sid = sort(keys(%cacheh));

		# label the first and last elements with their integer value
		my $num = scalar(@sid) - 1;

		$sid[0] .= " = 0";
		$sid[-1] .= " = $num";

		$bigstr .= "enum SysCacheIdentifier\n{\n\t" .
			join(",\n\t", @sid) .
			"\n};\n";
	}

	my $prefx = quotemeta('TIDYCAT_BEGIN_CODEGEN');
	my $suffx = quotemeta('TIDYCAT_END_CODEGEN');

	my @zzz = ($whole_file =~ 
			   m/^\s*\/\*\s*$prefx\s*\s*$(.*)^\s*\/\*\s*$suffx\s*\*\/\s*$/ms);

	die "bad target: $whole_file"
		unless (scalar(@zzz));

	my $rex = $zzz[0];

	# replace carriage returns first, then quotemeta, then fix CR again...
	$rex =~ s/\n/SLASHNNN/gm;
	$rex = quotemeta($rex);
	$rex =~ s/SLASHNNN/\\n/gm;
	
	# substitute the new generated definitions for the prior
	# generated definitions in the target file
	$whole_file =~ s/$rex/$bigstr/ms;

	my $outi;
	
	open $outi, "> $tmphname"
		or die "cannot open $tmphname for write: $!";
	
	# rewrite the target file
	print $outi $whole_file;
	
	# now do syscache.c
	fixup_syscache_cfile($alltabs, \%cacheh, $fullcname, $tmpcname, 
						 $gen_hdr_str);

} # end fixup_syscache

sub formattoastheaders
{
	my $alltabs = shift;
	my $bigstr = "";
	my %relidh;

	# nicer to sort by relid (vs tablename)
	while (my ($jj, $ww) = each(%{$alltabs}))
	{
		$relidh{$ww->{with}->{relid}} = $jj;
	}

#	for my $kk (sort (keys (%{$alltabs})))
	for my $jj (sort {$a <=> $b} (keys (%relidh)))
	{
		my $kk = $relidh{$jj};
		my $vv = $alltabs->{$kk};
		my $relid = $vv->{with}->{relid};
		my $rct   = $vv->{relid_comment_tag}; # standard comment

		next unless (exists($vv->{with}->{toast_oid}));

		{
			my $camelname;

			$camelname = $vv->{with}->{camelcase};
			
			# maintain PG/GP prefix for the camelname for TOAST.
			# weird, but true...
			if ($kk =~ m/pg/i)
			{
				$camelname = "Pg" . $camelname
					unless ($camelname =~ m/pg/i);
			}
			elsif ($kk =~ m/gp/i)
			{
				$camelname = "Gp" . $camelname
					unless ($camelname =~ m/gp/i);
			}


			$bigstr .= $rct;
			$bigstr .= "DECLARE_TOAST(";
			$bigstr .=
				$kk . ", " . $vv->{with}->{toast_oid} . ", " .
				$vv->{with}->{toast_index} . ");\n";

			my $toasttabname = $camelname . "ToastTable";

			my $ccdef = "#define " . $toasttabname;

			$bigstr .= simpletabalign($ccdef, 
									  $vv->{with}->{toast_oid}) . "\n";


			my $toastindname = $camelname . "ToastIndex";

			$ccdef = "#define " . $toastindname;

			$bigstr .= simpletabalign($ccdef, 
									  $vv->{with}->{toast_index}) . "\n\n\n";

			$vv->{CamelCaseToastTab} = $toasttabname;
			$vv->{CamelCaseToastInd} = $toastindname;
		}
	}

	return $bigstr;
} # end formattoastheaders

sub formattab
{
	my ($alltabs, $keys) = @_;
	my $bigstr = "";

	for my $kk (@{$keys})
	{
		my $vv = $alltabs->{$kk};
		my $relid = $vv->{with}->{relid};

		$bigstr .= "\n/*\n TidyCat Comments for $kk:\n";
		$bigstr .= "  Table is shared, so catalog.c:IsSharedRelation is " .
			"updated.\n" if $vv->{with}->{shared};
		if ($vv->{with}->{bootstrap})
		{
			$bigstr .= "  Table is a **bootstrap** table.\n";
		}
		if ($vv->{with}->{oid})
		{
			$bigstr .= "  Table has an Oid column.\n";
		}
		else
		{
			$bigstr .= "  Table does not have an Oid column.\n";
		}
		if (exists($vv->{with}->{reltype_oid}))
		{
			$bigstr .= "  Table has static type (see pg_types.h).\n"; 
		}
		else
		{
			$bigstr .= "  Table does not have static type " .
				"(only legal for pre-3.3 tables). \n"; 
		}

		# TOAST comments
		if (exists($vv->{with}->{toast_oid}))
		{
			$bigstr .= "  Table has TOASTable columns,";
			if (exists($toast_reltype_exception_h{$kk}))
			{
				$bigstr .= " but TOAST table does not have static type.\n"; 
			}
			else
			{
				$bigstr .= " and TOAST table has static type.\n"; 
			}
		}
		else
		{
			if (exists($toast_tab_exception_h{$kk}))
			{
				$bigstr .= "  Table has TOASTable columns, but NO TOAST table.\n"; 
			}
		}

		if (exists($vv->{with}->{content}))
		{
			if ($vv->{with}->{content} =~ m/MASTER/)
			{
				$bigstr .= "  Table contents are only maintained on MASTER.\n";
			}
			elsif ($vv->{with}->{content} =~ m/PERSISTENT/)
			{
				$bigstr .= "  Table contents maintain PERSISTENT objects in special way.\n";
			}
			elsif ($vv->{with}->{content} =~ m/SEGMENT/)
			{
				$bigstr .= "  Table contents are local to each SEGMENT.\n";
			}
		}

		$bigstr .= "  Table has weird hack for timestamp column.\n " 
			if (exists($vv->{tzhack}));
		$bigstr .= "  Table has weird hack for time column.\n " 
			if (exists($vv->{tmhack}));

		$bigstr .= "\n*/\n\n";

		$bigstr .= formatTZcomment($vv->{tzhack})
			if (exists($vv->{tzhack}));
		$bigstr .= formatTMcomment($vv->{tmhack})
			if (exists($vv->{tmhack}));

		$bigstr .= getcomment1($kk);

		$vv->{CamelCaseRelationId} = $vv->{with}->{camelcase} . "RelationId" ;

		my $ccdef = '#define ' . $vv->{CamelCaseRelationId};

		$bigstr .= simpletabalign($ccdef, $relid) . "\n\n";
			
		$bigstr .= 'CATALOG('. $kk . ',' . $relid . ')';
		$bigstr .= " BKI_BOOTSTRAP" if $vv->{with}->{bootstrap};
		$bigstr .= " BKI_SHARED_RELATION" if $vv->{with}->{shared};
		$bigstr .= " BKI_WITHOUT_OIDS" unless $vv->{with}->{oid};
		$bigstr .= "\n{";

		$bigstr .= formatcols($vv->{cols});

		# build the Form/FormData strings
		my $tform     = struct_form_tname($kk);
		my $tformdata = struct_form_tname($kk);
		$tformdata =~ s/Form/FormData/;

		$bigstr .= "} " . $tformdata . ";\n";

		$bigstr .= "\n#undef timestamptz\n"
			if (exists($vv->{tzhack}));
		$bigstr .= "\n#undef time\n"
			if (exists($vv->{tmhack}));

		$bigstr .= "\n\n";

		$bigstr .= getcomment2($kk, $tform);

		$bigstr .= "typedef " . $tformdata . " *" . $tform . ";\n";

		$bigstr .= "\n\n";

		$bigstr .= getcomment3($kk);

		$bigstr .= formatcolconst($kk, $vv->{cols});

	}

	return $bigstr;

} # end formattab

sub formattabs
{
	my $alltabs = shift;
	my $bigstr = "";

	for my $kk (sort (keys (%{$alltabs})))
	{
		$bigstr .= formattab($alltabs, [$kk]);
	}

	return $bigstr;

} # end formattabs


sub parsefile
{
	my ($file_name, $finddefs) = @_;

	# if not looking for tidycat definitions, just find the code gen
	# sections

	my $begin_def_str = "TIDYCAT_BEGIN_CODEGEN";
	my $end_def_str = "TIDYCAT_END_CODEGEN";
	
	if (defined($finddefs))
	{
		$begin_def_str = "TIDYCAT_BEGINDEF";
		$end_def_str = "TIDYCAT_ENDDEF";

		# XXX XXX: if only dumping sql definitions, allow "fake" ones...
		if ($glob_glob->{sqldef} || $glob_glob->{dumpdef} 
			|| $glob_glob->{syscache})
		{
			$begin_def_str .= "|TIDYCAT_BEGINFAKEDEF";
			$end_def_str .= "|TIDYCAT_ENDFAKEDEF";

		}

	}


	my $entrylist = [];

	my $bigstr ;

	open my $file_in, "< $file_name" or die "cannot open $file_name: $!";

	my $phase = "begindef"; # begindef, enddef, begingen, endgen

	my $ii = 0;

	my $entry;

	for my $ini (<$file_in>)
	{
		$ii++;
		if ($phase =~ m/enddef/)
		{
			if ($ini =~ m/$end_def_str/)
			{
				if ($ini =~ m/\*\//)
				{
					# look for trailing "*/" 
					$entry->{enddef} = $ii;
				}
				else
				{
					# probably the next line
					$entry->{enddef} = $ii + 1;
				}

				$entry->{def} = $bigstr;

				if (defined($finddefs))
				{
					$phase = "begingen";
				}
				else
				{
					# save the current entry
					push @{$entrylist}, $entry;
					$phase = "begindef"; 
				}

				next;
			}

			# append to the string!!
			$bigstr .= $ini;

		}
		elsif ($phase =~ m/begindef/)
		{
			next unless ($ini =~ m/$begin_def_str/);

			$entry = {begindef => $ii};
			$bigstr = "";
			$phase = "enddef";

			next;
		}
		elsif ($phase =~ m/begingen/)
		{
			# might have new definition and no codegen
			if ($ini =~ m/$begin_def_str/)
			{
				push @{$entrylist}, $entry;

				$entry = {begindef => $ii};
				$bigstr = "";
				$phase = "enddef";

				next;

			}
			elsif ($ini =~ m/TIDYCAT\_BEGIN\_CODEGEN/)
			{
				$entry->{begin_codegen} = $ii;
				$phase = "endgen";

			}
			else
			{
				# stop looking if no generated code within 3 lines...

				if (($ii - $entry->{enddef}) > 3)
				{
					# save the current entry
					push @{$entrylist}, $entry;
					$phase = "begindef"; 
				}
				next;
			}

		}
		elsif ($phase =~ m/endgen/)
		{
			if ($ini =~ m/TIDYCAT\_END\_CODEGEN/)
			{
				$entry->{end_codegen} = $ii;

				# save the current entry
				push @{$entrylist}, $entry;


				$bigstr = "";
				$phase = "begindef";

				
			}
			next;


		}

	} # end for ini

	die "unterminated tidycat definition in $file_name at " .
		$entry->{begindef} if (defined($entry) && ($phase =~ m/enddef/));

	if (defined($entry) && ($phase =~ m/begingen/))
	{
		push @{$entrylist}, $entry;
	}

	close $file_in;

	return $entrylist;
} # end parsefile

sub bufferfile
{
	my $file_name = shift;

	open my $file_in, "< $file_name" or die "cannot open $file_name: $!";

	my $buflist = [];

	for my $ini (<$file_in>)
	{
		push @{$buflist}, $ini;
	}
	close $file_in;

	return $buflist;
}

# We may already have previously generated code for a relation.  Need
# to filter it out to eliminate duplicate definitions.  Note that this
# only works as long as the relid does not change.
sub clean_duplicate_entries
{
	my ($alltabs, $basename, $gen_code) = @_;

	my $db1 = 1;

	print $basename, ":\n\n"
		if ($db1);

	# NOTE: list the names of special files with generated code
	return $gen_code
		unless ($basename =~ m/^(indexing\.h|toasting\.h|toasting\.c|catalog\.c toast|catalog\.c ind|catalog\.c tab|bootparse\.y|pg\_type\.h)$/);

	my $skiplines = 0;

	# skip N lines of generated code for duplicate entries
	$skiplines = 2 
		if ($basename =~ m/indexing/);
	$skiplines = 3 
		if ($basename =~ m/toasting\.h/);
	$skiplines = 3 
		if ($basename =~ m/toasting\.c/);
	$skiplines = 2 
		if ($basename =~ m/pg\_type/);
	$skiplines = 1
		if ($basename =~ m/catalog.*ind/);
	$skiplines = 1
		if ($basename =~ m/catalog.*tab/);
	$skiplines = 2
		if ($basename =~ m/catalog.*toast/);
	$skiplines = 3
		if ($basename =~ m/bootparse/);

	for my $kk (sort (keys (%{$alltabs})))
	{
		my $vv = $alltabs->{$kk};
		my $relid = $vv->{with}->{relid};
		my $rct   = $vv->{relid_comment_tag}; # standard comment

		$rct = '/* relation id: ' . $relid;

		next # no match for current relation
			unless ($gen_code =~ m/$rct\s+/);

		next # don't cleanup unless have lines to skip...
			unless ($skiplines);

		my @foo = split (/\n/, $gen_code);

		my @baz;

		while (scalar(@foo))
		{
			my $lin = shift @foo;

			# iter by line, but skip the line which matches the
			# "relation id" comment
			if ($lin !~ m/$rct\s+/)
			{
				push @baz, $lin;
			}
			else
			{
				print "skip: $lin\n"
					if ($db1);

				# and the subsequent N generated lines after the comment
				for my $ii (1..$skiplines)
				{
#					print "skip: ",
					shift @foo
						if (scalar(@foo));
#					print "\n";
				} # end for
			}
		} # end while foo
			
		$gen_code = join ("\n", @baz);

	} # end for kk

	return $gen_code;

} # end clean_duplicate_entries

sub fixup_generated_code
{
	my ($alltabs, $basename, $fullname, $tmpname, $new_gen_list, 
		$verzion, $nnow,
		$fixup_ref) = @_;

	my $elist_index = 1;

	# catalog.c toast case is third, and index case is second set of
	# definitions
	$elist_index = 3
		if ($basename =~ m/catalog\.c toast/);
	$elist_index = 2
		if ($basename =~ m/catalog\.c ind/);

	return
		unless (scalar(@{$new_gen_list}));

	my ($elist, $buffil);

	# handle toast, then index
	if (defined($fixup_ref) && (2 <= scalar(@{$fixup_ref})))
	{
		$elist = shift @{$fixup_ref};
		$buffil = shift @{$fixup_ref};
	}
	else
	{
		$elist = parsefile($fullname);
		$buffil = bufferfile($fullname);
	}

	# XXX XXX XXX XXX: dump of regen
#	print $basename,":\n", Data::Dumper->Dump($elist), "\n";

	return
		unless (scalar(@{$elist}) >= $elist_index);

	my $entry = $elist->[$elist_index - 1];

	my $bigstr = $entry->{def};

	$bigstr = clean_duplicate_entries($alltabs, $basename, $bigstr);

#	print "bigstr: ", $bigstr, "XXX\n";

	my $genstr = "Generated by " . $verzion;

	$bigstr =~ s/Generated by.*tidycat\.pl\s+version\s+\d+\.(\d)*/$genstr/m;

	$bigstr =~ 
s/($genstr)\n(\s+on\s+)(\w+\s+\w+\s+\d+\s+\d+\:\d+\:\d+\s+\d+)/$1\n$2$nnow/m;

	$bigstr .= "\n" . join("", @{$new_gen_list});

	# remove double-newlines
	$bigstr =~ s/\n\n/\n/gm;

	if (exists($entry->{begindef}))
	{
		splice (@{$buffil}, 
				($entry->{begindef}),
				($entry->{enddef} - $entry->{begindef})-1,
				$bigstr);
	}

	# write the modified file to the temp directory
	open my $file_out, "> " . $tmpname
		or die "Cannot open $tmpname for write";

	# write the modified file to the tmpdir
	print $file_out join("", @{$buffil});
	
	close $file_out;

	return [$elist, $buffil];

} # end fixup_generated_code

sub fixup_pg_tidy
{
	my ($h_allfiles, $fullname, $tmpname,
		$verzion, $nnow) = @_;

	my $bigstr = <<'EOF_bigstr';
/*-------------------------------------------------------------------------
 *
 * pg_tidycat.h
 *	  
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
GENCOM
 *-------------------------------------------------------------------------
 */


EOF_bigstr

	my $gen_hdr_str = " * WARNING: DO NOT MODIFY THIS FILE: \n" .
	" *  Generated by " . $verzion . "\n" . 
	" *  on " . $nnow ;

	$bigstr =~ s/GENCOM/$gen_hdr_str/gm;

	my $prevhdr = `grep include $fullname`;

	my @lines = split(/\n/, $prevhdr);

	
	for my $lin (@lines)
	{
		next unless ($lin =~ m/^\s*\#\s*include\s*\"catalog\//);
		my @foo = ($lin =~ m/^\s*\#\s*include\s*\"catalog\/(.*)\"/);

		my $fname = $foo[0];
		
		next if (exists($allfiles_exception_h{$fname}));

		$h_allfiles->{$fname} = 1;
	}

	# build sorted list of all names in 
	# include "catalog/pg_..." format

	$bigstr .= '#include "catalog/' .
		join("\"\n#include \"catalog/", 
			 sort(keys(%{$h_allfiles}))) . "\"\n";

	# write the modified file to the temp directory
	open my $file_out, "> " . $tmpname
		or die "Cannot open $tmpname for write";

	# write the modified file to the tmpdir
	print $file_out $bigstr;
	
	close $file_out;
	
} # end fixup_pg_tidy

sub fixup_makefile
{
	my ($h_allfiles, $fullname, $tmpname,
		$verzion, $nnow) = @_;

	# here doc to replace tidycat_bki_src defs in Makefile
	my $bigstr = <<'EOF_bigstr';

# TIDYCAT_BEGIN_CODEGEN 
#GENHDRSTR
TIDYCAT_BKI_SRCS := \
	GENSTUFF

# TIDYCAT_END_CODEGEN 
EOF_bigstr

	my $whole_file;

	{
        # $$$ $$$ undefine input record separator (\n)
        # and slurp entire file into variable

        local $/;
        undef $/;

		my $fh;

		open $fh, "< $fullname" or die "cannot open $fullname: $!";

		$whole_file = <$fh>;
		
		close $fh;
	}

	my $prefx = quotemeta('TIDYCAT_BEGIN_CODEGEN');
	my $suffx = quotemeta('TIDYCAT_END_CODEGEN');

	my @zzz = ($whole_file =~ m/^\s*\#\s*$prefx\s*$(.*)^\s*\#\s*$suffx\s*$/ms);

	return 
		unless (scalar(@zzz));

	my @lines = split(/\n/, $zzz[0]);

	for my $lin (@lines)
	{
		next unless ($lin =~ m/\.h/);

		# capture the filename
		my @foo = ($lin =~ m/^\s*(.*)\s*(?:\\)?\s*$/);

		my $fname = $foo[0];
		# trim leading and trailing spaces
		$fname =~ s/\\$//;
		$fname =~ s/^\s*//;
		$fname =~ s/\s*$//;
		
		next if (exists($allfiles_exception_h{$fname}));

		$h_allfiles->{$fname} = 1;
	}

	# build sorted list of all names prefixed with tab and ending with
	# slash, eg:
	#  pg_foo.h \ ...

	my $genstuff = join(" \\\n\t",
						sort(keys(%{$h_allfiles})));

	$bigstr =~ s/GENSTUFF/$genstuff/;

	my $gen_hdr_str = " WARNING: DO NOT MODIFY THE FOLLOWING SECTION: \n" .
		"# Generated by " . $verzion . "\n" . 
		"# on " . $nnow . "\n\n";

	$bigstr =~ s/GENHDRSTR/$gen_hdr_str/;

	# write the modified file to the temp directory
	open my $file_out, "> " . $tmpname
		or die "Cannot open $tmpname for write";

	# replace with modified expression
	$whole_file =~ s/^\s*\#\s*$prefx\s*$(.*)^\s*\#\s*$suffx\s*$/$bigstr/ms;

	# write the modified file to the tmpdir
	print $file_out $whole_file;
	
	close $file_out;
	
} # end fixup_makefile

if (1)
{
# 5000 table
# 6000 indexes

    my $verzion = "unknown";
	$verzion = $glob_glob->{_sleazy_properties}->{version}
	if (exists($glob_glob->{_sleazy_properties}) &&
		exists($glob_glob->{_sleazy_properties}->{version}));

	$verzion = $0 . " version " . $verzion;
	my $nnow = localtime;
	my $gen_hdr_str = "/* TIDYCAT_BEGIN_CODEGEN \n\n";
	$gen_hdr_str .= "   WARNING: DO NOT MODIFY THE FOLLOWING SECTION: \n" .
		"   Generated by " . $verzion . "\n" . 
		"   on " . $nnow . "\n*/\n\n";

	my ($vol0, $dirs0, $base0) = File::Spec->splitpath($0);

	# make sure have a directory for tidypath location
	$dirs0 = File::Spec->rel2abs(".") unless (length($dirs0));

#	print "dirs0: $dirs0\n";

	my @backup_files; # list of files to backup

	my $tmp_indexing = File::Spec->catfile($glob_tmpdir, "indexing.h");
	my $fil_indexing = File::Spec->catfile($dirs0, "indexing.h");
	my $buf_indexing;
	my @arr_indexing;

	push @backup_files, $fil_indexing;

	my $tmp_toastingh = File::Spec->catfile($glob_tmpdir, "toasting.h");
	my $fil_toastingh = File::Spec->catfile($dirs0, "toasting.h");
	my $buf_toastingh;
	my @arr_toastingh;

	push @backup_files, $fil_toastingh;

	my $tmp_pg_type = File::Spec->catfile($glob_tmpdir, "pg_type.h");
	my $fil_pg_type = File::Spec->catfile($dirs0, "pg_type.h");
	my $buf_pg_type;
	my @arr_pg_type;

	push @backup_files, $fil_pg_type;

	# dirs0 should be src/include/catalog
	my @srcdirs = File::Spec->splitdir($dirs0);
	push @srcdirs, "..";
	push @srcdirs, "..";

	# src/backend/catalog
	my $tmp_catalogc = File::Spec->catfile($glob_tmpdir, "catalog.c");
	my $fil_catalogc = File::Spec->catfile(
		File::Spec->catdir(@srcdirs, "backend", "catalog"), "catalog.c");
	my $buf_catalogc;
	my @arr_catalogc;
	my @arr_catalogc_tab;
	my @arr_catalogc_ind;
	my @arr_catalogc_toast;

	push @backup_files, $fil_catalogc;

	my $tmp_toastingc = File::Spec->catfile($glob_tmpdir, "toasting.c");
	my $fil_toastingc = File::Spec->catfile(
		File::Spec->catdir(@srcdirs, "backend", "catalog"), "toasting.c");
	my $buf_toastingc;
	my @arr_toastingc;

	push @backup_files, $fil_toastingc;

	# src/backend/bootstrap
	my $tmp_bootparse = File::Spec->catfile($glob_tmpdir, "bootparse.y");
	my $fil_bootparse = File::Spec->catfile(
		File::Spec->catdir(@srcdirs, "backend", "bootstrap"), "bootparse.y");
	my $buf_bootparse;
	my @arr_bootparse;

	push @backup_files, $fil_bootparse;

	# src/backend/catalog/Makefile
	my $tmp_catmakem = File::Spec->catfile($glob_tmpdir, "Makefile");
	my $fil_catmakem = File::Spec->catfile(
		File::Spec->catdir(@srcdirs, "backend", "catalog"), "Makefile");
	my $buf_catmakem;
	my @arr_catmakem;

	push @backup_files, $fil_catmakem;

	# src/include/catalog/pg_tidycat.h
	my $tmp_pg_tidy = File::Spec->catfile($glob_tmpdir, "pg_tidycat.h");
	my $fil_pg_tidy = File::Spec->catfile($dirs0, "pg_tidycat.h");
	my $buf_pg_tidy;
	my %h_pg_tidy;

	push @backup_files, $fil_pg_tidy;

	# src/include/utils/syscache.h
	my $tmp_syscacheh = File::Spec->catfile($glob_tmpdir, "syscache.h");
	my $fil_syscacheh = File::Spec->catfile( 
		File::Spec->catdir(@srcdirs, "include", "utils"), "syscache.h");

	push @backup_files, $fil_syscacheh;

	# src/backend/utils/cache/syscache.c
	my $tmp_syscachec = File::Spec->catfile($glob_tmpdir, "syscache.c");
	my $fil_syscachec = File::Spec->catfile( 
		File::Spec->catdir(@srcdirs, "backend", "utils", "cache"), 
		"syscache.c");

	push @backup_files, $fil_syscachec;

	my $backup_dir;

	# backup files that may get changed
	unless ($glob_glob->{sqldef} ||
			($glob_glob->{dumpdef}))
	{
		$backup_dir = 
			File::Spec->catfile($glob_tmpdir, "tidycat_backup." . $$);

		die "Cannot open backup directory $backup_dir : $! "
			unless (mkdir($backup_dir));
		
		my $cpargs = join(" ", @backup_files);

		my $cpstat = `cp $cpargs $backup_dir`;

	}

	my %alltabs;

	my @arr_sql; # array of sql statement output
	my %dumptabs_h; # hash of table definitions

	for my $filnam (@ARGV)
	{
		%alltabs = ();

		my ($vol, $dirs, $basename) = File::Spec->splitpath($filnam);

		my $elist = parsefile($filnam, 1); # file has def and codegen sections

		my $buffil = bufferfile($filnam);

		# do it in reverse order to strip out line numbering correctly
		for my $entry (reverse (@{$elist}))
		{
			my $bigstr = $entry->{def};
			
#	print $bigstr, "\n";

			# tricky bit here: might have multiple CREATE table
			# statements in a tidycat definition entry, and multiple
			# tidycat defs in a single file.  So build the list of all
			# table definitions in alltabs, but extract the set of
			# keys (tablenames) for the current entry in parsetabdef,
			# so we can feed that to formattab and generate the code
			# for just the current entry.

			my @keys = parsetabdef(\%alltabs, $bigstr, $filnam);

#	print Data::Dumper->Dump([%alltabs]);

#			print "fff:", formattabs(\%alltabs);

			my $gentext = [];

			push @{$gentext}, split(/(\n)/, $gen_hdr_str);

			push @{$gentext}, split(/(\n)/, formattab(\%alltabs, \@keys));

			push @{$gentext}, "\n\n/* TIDYCAT_END_CODEGEN */\n";

			if (exists($entry->{begin_codegen}))
			{
				splice (@{$buffil}, 
						($entry->{begin_codegen}-1),
						($entry->{end_codegen} - $entry->{begin_codegen}) + 1,
						@{$gentext});
			}
			else
			{

				splice (@{$buffil}, ($entry->{enddef}),
						0,
						@{$gentext});
			}
		} # end for entry

		push @arr_indexing, formatindexes(\%alltabs);

		push @arr_toastingh, formattoastheaders(\%alltabs);
		
		push @arr_pg_type, formattypes(\%alltabs);
		push @arr_toastingc, fix_toastbootstrap(\%alltabs);

		my $tab_ind = fix_issharedrelation_function(\%alltabs);

#		print Data::Dumper->Dump($tab_ind), "\n";

		push @arr_catalogc, $tab_ind;

		if (defined($tab_ind) && scalar(@{$tab_ind}))
		{
			push @arr_catalogc_tab, $tab_ind->[0]
				if (length($tab_ind->[0]));
			push @arr_catalogc_ind, $tab_ind->[1]
				if ((1 < scalar(@{$tab_ind})) && (length($tab_ind->[1])));
			push @arr_catalogc_toast, $tab_ind->[2]
				if ((2 < scalar(@{$tab_ind})) && (length($tab_ind->[2])));
		}

		push @arr_bootparse, fix_bootparse(\%alltabs);
		
		# store the filenames for pg_tidycat and Makefile fixup
		get_all_filenames(\%alltabs, \%h_pg_tidy);

		if ($glob_glob->{sqldef})
		{
			while (my ($jj, $ww) = each(%alltabs))
			{
				if (exists($ww->{tabdef_text})
					&& length($ww->{tabdef_text})
					&& ($ww->{tabdef_text} =~ m/CREATE\s+TABLE/i))
				{
					push @arr_sql, $ww->{tabdef_text};
				}
			}

			next; # don't update file only want sql
		}
		
		
		if (1)
		{
			while (my ($jj, $ww) = each(%alltabs))
			{
				$dumptabs_h{$jj} = $ww;
			}

			next # don't update file if dumping definitions
				if ($glob_glob->{dumpdef});
		}

		# backup the original version
		my $cpstat = `cp $filnam $backup_dir`;

		# write the modified file to the temp directory
		open my $file_out, "> " . File::Spec->catfile($glob_tmpdir, $basename)
			or die "Cannot open $basename for write in $glob_tmpdir";

		# write the modified file to the tmpdir
		print $file_out join("", @{$buffil});

		close $file_out;

	} # end for filnam

#	print "tmp: $tmp_indexing\tfil: $fil_indexing\n";

	# get catalog version if possible
	my $fil_catversion = File::Spec->catfile($dirs0, "catversion.h");
	my $catvstr = "";

	if (-e $fil_catversion)
	{
		$catvstr = `grep 'CATALOG_VERSION_NO' $fil_catversion`;

		if (length($catvstr))
		{
			my @cv = ($catvstr =~ m/(\d+)/im);
			$catvstr = "";
			$catvstr = $cv[0] if (scalar(@cv));
		}
	}

	if ($glob_glob->{syscache})
	{
		fixup_syscache(\%dumptabs_h, 	
						$fil_syscacheh, $tmp_syscacheh, 					
						$fil_syscachec, $tmp_syscachec, 					
						$verzion, $nnow);
	}

	if ($glob_glob->{sqldef})
	{
		open my $file_out, "> " . $glob_glob->{sqldef}
			or die "Cannot open $glob_glob->{sqldef} for write : $!";

		print $file_out 
			"-- Generated by " . $verzion . "\n-- on " . $nnow . "\n";

		if (length($catvstr))
		{
			print $file_out "-- CATALOG_VERSION_NO = " . $catvstr . "\n";
		}

		print $file_out "\n";
		print $file_out join(";\n", @arr_sql), ";\n";

		close $file_out;
		exit();
	}
	if ($glob_glob->{dumpdef})
	{
		open my $file_out, "> " . $glob_glob->{dumpdef}
			or die "Cannot open $glob_glob->{dumpdef} for write : $!";

		local $Data::Dumper::Sortkeys = 1;

        # JSON might not be installed, so test for it.

		if ($glob_glob->{dumpformat} =~ m/j/)
		{
			if (eval "require JSON")
			{
				# because JSON is REQUIREd, not USEd, the symbols are not
				# imported into the environment.

				# add a comment and sort the keys
				my $cmt = "Generated by $verzion on $nnow";
				$cmt .= " CATALOG_VERSION_NO=$catvstr"
					if (length($catvstr));
				$dumptabs_h{"__comment"} = $cmt;
				$dumptabs_h{"__info"} = {CATALOG_VERSION_NO => $catvstr}
					if (length($catvstr));

				my $whole_jfil = JSON::to_json(\%dumptabs_h, 
											   {pretty => 1, indent => 2, 
												canonical => 1});

				# remove date strings from relid_comment_tags to
				# prevent diffs and assuage Caleb's discomfiture
				#
				# s|20yymmdd */| */| 
				#
				$whole_jfil =~ s|\s+20(\d){6}\s+\*\/| \*/|gm;

				print $file_out $whole_jfil;
			}
			else
			{
				die("Fatal Error: The required package JSON is not installed -- please download it from www.cpan.org\n");
			}
		}
		else
		{
			print $file_out Data::Dumper->Dump([\%dumptabs_h]),"\n";
		}

		close $file_out;
		exit();
	}

	fixup_generated_code (\%alltabs, 
						  "indexing.h", $fil_indexing, $tmp_indexing,
		\@arr_indexing, $verzion, $nnow)
		if (scalar(@arr_indexing));

	fixup_generated_code (\%alltabs, 
						  "toasting.h", $fil_toastingh, $tmp_toastingh,
		\@arr_toastingh, $verzion, $nnow)
		if (scalar(@arr_toastingh));
	fixup_generated_code (\%alltabs, 
						  "toasting.c", $fil_toastingc, $tmp_toastingc,
		\@arr_toastingc, $verzion, $nnow)
		if (scalar(@arr_toastingc));

	fixup_generated_code (\%alltabs, 
						  "pg_type.h", $fil_pg_type, $tmp_pg_type,
		\@arr_pg_type, $verzion, $nnow)
		if (scalar(@arr_pg_type));

#	print Data::Dumper->Dump(\@arr_catalogc_tab), "\n";

	# do toast entries first because they are last (keep line
	# numbering consistent for prior items), then index, then table
	#
	# catalog.c IsSharedRelation index entries
	my ($catc_ref, $catc_ref2);

	$catc_ref2 = 
		fixup_generated_code (\%alltabs, 
							  "catalog.c toast", $fil_catalogc, $tmp_catalogc,
							  \@arr_catalogc_toast, $verzion, $nnow)
		if (scalar(@arr_catalogc_toast));

	if (scalar(@arr_catalogc_ind))
	{
		$catc_ref = 
		fixup_generated_code (\%alltabs, 
						  "catalog.c ind", $fil_catalogc, $tmp_catalogc,
							  \@arr_catalogc_ind, $verzion, $nnow, $catc_ref2);
	}
	else
	{
		$catc_ref = $catc_ref2;
	}

	# catalog.c IsSharedRelation table entries
	fixup_generated_code (\%alltabs, 
						  "catalog.c tab", $fil_catalogc, $tmp_catalogc,
						  \@arr_catalogc_tab, $verzion, $nnow, $catc_ref)
		if (scalar(@arr_catalogc_tab));

	# bootparse.y static type entries
	fixup_generated_code (\%alltabs, 
						  "bootparse.y", $fil_bootparse, $tmp_bootparse,
						  \@arr_bootparse, $verzion, $nnow)
		if (scalar(@arr_bootparse));
	
	if (scalar(keys(%h_pg_tidy)))
	{
		fixup_pg_tidy (\%h_pg_tidy, $fil_pg_tidy, $tmp_pg_tidy,
			$verzion, $nnow);
		fixup_makefile (\%h_pg_tidy, $fil_catmakem, $tmp_catmakem,
			$verzion, $nnow);
	}

}

exit();


# SLZY_TOP_BEGIN
if (0)
{
    my $bigstr = <<'EOF_bigstr';
{
   "args" : [
      {
         "alias" : "?",
         "long" : "Print a brief help message and exits.",
         "name" : "help",
         "required" : "0",
         "short" : "brief help message",
         "type" : "untyped"
      },
      {
         "long" : "Prints the manual page and exits.",
         "name" : "man",
         "required" : "0",
         "short" : "full documentation",
         "type" : "untyped"
      },
      {
         "alias" : "dd",
         "long" : "$ddlong",
         "name" : "dumpdef",
         "short" : "output file for dump of serialized catalog data structures",
         "type" : "outfile"
      },
      {
         "alias" : "df|dumpfmt",
         "long" : "Specify a format for the dumpfile.  The only valid options are jason or perl.",
         "name" : "dumpformat",
         "short" : "format options for dump file [perl, jason]",
         "type" : "outfile"
      },
      {
         "long" : "$sqldeflong",
         "name" : "sqldef",
         "short" : "output file for dump of catalog DDL statements",
         "type" : "outfile"
      },
      {
         "long" : "$syscachelong",
         "name" : "syscache",
         "short" : "build syscache entries",
         "type" : "untyped"
      }
   ],
   "long" : "$toplong",
   "properties" : {
      "COPYDATES" : "2009-2012",
      "slzy_date" : 1350599086
   },
   "short" : "generate catalog entries",
   "version" : "34"
}

EOF_bigstr
}
# SLZY_TOP_END

# SLZY_LONG_BEGIN
if (0)
{
	my $ddlong = <<'EOF_ddlong';
    
Specify an optional filename to hold a dump of the serialized catalog
data structures.  The format of the dump file is determined by
dumpformat
EOF_ddlong

my $syscachelong = <<'EOF_syscachelong';

If specified, rebuild syscache.h and syscache.c.  Note that this
option, like dumpdef, must read all catalog headers, ie in
src/include/catalog, the command:

   perl tidycat.pl -syscache *.h

constructs new versions of syscache.c and syscache.h.  

NOTE: Modification and extension of syscache entries is extremely rare.  
Usage of this option is discouraged.


EOF_syscachelong

my $sqldeflong = <<'EOF_sqldeflong';
    
Specify an optional filename to hold the CREATE TABLE statements from
the tidycat definitions.  Note that these statements will contain the
tidycat WITH clause, which is not valid SQL.
EOF_sqldeflong

	my $toplong = <<'EOF_longstr';
tidycat.pl handles all of your stinky catalog problems, leaving a
fresh, clean scent.  Catalog tables require several sets of
co-ordinated modifications to multiple source files to define the
table and indexes, and (under some circumstances) the toast tables and
indexes (in toasting.h and toasting.c), as well as some special code
in catalog.c and bootparse.y to aid in bootstrap and upgrade.  tidycat
also updates a generated list of headers in pg_tidycat.h and the
catalog Makefile.  The original files are copied to a special
tidycat_backup directory in /tmp, and all generated files are written
to /tmp.  tidycat.pl uses a single definition statement to generate
the code associated with the table in multiple source files.  A sample
definition for the fictional pg_foobar.h follows:

  /* TIDYCAT_BEGINDEF

   CREATE TABLE pg_foobar
   with (camelcase=FooBar, shared=true, oid=true, relid=9991, reltype_oid=9992)
   (
   fooname name, -- name of foo bar
   foolimit real, -- max active count limit
   fooignore boolean, -- ignore foo in baz context
   );

   create unique index on pg_foobar(oid) with (indexid=9993);
   create index on pg_foobar(fooname) with (indexid=9994);

   TIDYCAT_ENDDEF
  */

The definition must begin and end with the
TIDYCAT_BEGINDEF/TIDYCAT_ENDDEF exactly as shown.  The CREATE TABLE
statement is almost identical to standard SQL, with the addition of a
special WITH clause for implementation-specific features of the
catalog entry.  Currently, the relid and reltype_oid must be specified
using unassigned oids from the unused_oids script.  The options are:

{PODOVER8}

{ITEM} CamelCase: (optional)

If your tablename is a compound name, the index definitions look a
little nicer if you define an appropriate camelcase name.  Otherwise,
the default version of the name is the tablename, minus the "pg_" prefix, 
initial letter capitalized.

{ITEM} shared: (false by default)

Whether the table is local to each database or shared by all.

{ITEM} oid: (true by default)

Whether the table has an auto-generated oid column.

{ITEM} relid: (required)

The relid of the table in pg_class.  Use unused_oids to find one.

{ITEM} reltype_oid: (required for all post-3.3 tables)

The static reltype in pg_type (necessary for upgrade).  Use
unused_oids to find one.

{ITEM} toast_oid: (required for all tables with text or array columns)

The oid of the toast table (see toasting.h).  Use unused_oids to find
one.  tidycat will automatically detect if the table definition
requires a toast table and return an error if it is not specified.

{ITEM} toast_index: (required for all tables with toast_oid)

The oid of the index of the toast table (see toasting.h).  Use
unused_oids to find one.

{ITEM} toast_reltype: (required for all toast tables post-3.3)

The static reltype of the toast table in pg_type (necessary for
upgrade). Use unused_oids to find one.

{ITEM} content: (optional)

The "content" is only for catalog tables with non-standard content
management.  "Normal" catalog tables are replicated from the master to
all the segments.  Non-standard tables fall into three categories:
MASTER_ONLY, SEGMENT_LOCAL, and PERSISTENT. Don't add any new
non-standard tables.  Please.  Note that this flag controls the
generation of validation logic for checkcat; it does not control the
catalog table tuple replication mechanisms.

{PODBACK}

Similarly, index definitions are unique or non-unique, and require an
indexid (and an optional indexname).

Running tidycat.pl against pg_foobar.h adds the following section
after the definition:

  /* TIDYCAT_BEGIN_CODEGEN 
  
     WARNING: DO NOT MODIFY THE FOLLOWING SECTION: 
     Generated by tidycat.pl version 3.
     on Tue Dec  8 12:50:21 2009
  */
  
  
  /*
   TidyCat Comments for pg_foobar:
    Table is shared, so catalog.c:IsSharedRelation is updated.
    Table has an Oid column.
    Table has static type (see pg_types.h).
  
  */
  
  /* ----------------
   *		pg_foobar definition.  cpp turns this into
   *		typedef struct FormData_pg_foobar
   * ----------------
   */
  #define FooBarRelationId	9991
  
  CATALOG(pg_foobar,9991) BKI_SHARED_RELATION
  {
  	NameData	fooname;	/* name of foo bar */
  	float4		foolimit;	/* max active count limit */
  	bool		fooignore;	/* ignore foo in baz context */
  } FormData_pg_foobar;
  
  
  /* ----------------
   *		Form_pg_foobar corresponds to a pointer to a tuple with
   *		the format of pg_foobar relation.
   * ----------------
   */
  typedef FormData_pg_foobar *Form_pg_foobar;
  
  
  /* ----------------
   *		compiler constants for pg_foobar
   * ----------------
   */
  #define Natts_pg_foobar				3
  #define Anum_pg_foobar_fooname		1
  #define Anum_pg_foobar_foolimit		2
  #define Anum_pg_foobar_fooignore	3
  
  
  /* TIDYCAT_END_CODEGEN */
  

The generated code contains a CATALOG macro/struct definition for the
table, where the SQL datatypes are converted to C types.  The naming
and comments follow established conventions.

Additional modifications are made to indexing.h:

  /* relation id: 9991 - pg_foobar 20091208 */
  DECLARE_UNIQUE_INDEX(pg_foobar_oid_index, 9993, on pg_foobar using btree(oid oid_ops));
  #define FooBarOidIndexId 9993

  /* relation id: 9991 - pg_foobar 20091208 */
  DECLARE_INDEX(pg_foobar_fooname_index, 9994, on pg_foobar using btree(fooname name_ops));
  #define FooBarFoonameIndexId 9994


And the function IsSharedRelation() in catalog.c:
  bool 
  IsSharedRelation(Oid relationId)
  {
      /* These are the shared catalogs (look for BKI_SHARED_RELATION) */
      if (relationId == AuthIdRelationId ||
	(...much code...)
  /* relation id: 9991 - pg_foobar 20100105 */
  relationId == FooBarRelationId ||

Note that IsSharedRelation is only updated for shared tables.

And Boot_CreateStmt in bootparse.y:

  Boot_CreateStmt:
	  	  XCREATE optbootstrap optsharedrelation optwithoutoids boot_ident oidspec LPAREN
				{
	(...much code...)
  /* relation id: 9991 - pg_foobar 20100105 */
                            case FooBarRelationId:
                                typid = PG_FOOBAR_RELTYPE_OID;
                                break;

And pg_type.h:

  /* relation id: 9991 - pg_foobar 20100105 */
  DATA(insert OID = 9992 ( pg_foobar      PGNSP PGUID -1 f c t \054 9991 0 
	   record_in record_out record_recv record_send - d x f 0 -1 0 
       _null_ _null_ ));
  #define PG_FOOBAR_RELTYPE_OID 9992


{HEAD2} JSON document

In src/include/catalog, the command:

   perl tidycat.pl -dd foo.json -df json *.h

will generate a JSON document describing all of the catalog tables.
This file is installed under tools/bin/gppylib/data, and gpcheckcat
uses this data to generate check queries for foreign key constraint.

{HEAD1} CAVEATS

tidycat does not modify the original files -- it writes modified
versions of the files to /tmp.  You need to copy over the originals
with the generated files manually.  If you need to restore the
originals you can use the copies from the tidycat_backup directory.
Multiple cycles of tidycat with changing definitions can leave junk in
/tmp, and you might copy that junk into you source tree.  Do not copy
over any generated files that are older than your latest backup
directory.

EOF_longstr


}
# SLZY_LONG_END
